This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author conqp
Recipients conqp, xtreak, zzzeek
Date 2021-01-06.11:25:59
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1609932359.6.0.384727611958.issue41629@roundup.psfhosted.org>
In-reply-to
Content
I just stumbled across this issue trying to resolve this: https://bugs.python.org/issue42765?

While this fails:

from typing import NamedTuple


class Spamm(NamedTuple):

    foo: int
    bar: str

    def __getitem__(self, index_or_key):
        """Returns the respective item."""
        if isinstance(index_or_key, str):
            try:
                return getattr(self, index_or_key)
            except AttributeError:
                raise IndexError(index_or_key) from None

        return super().__getitem__(index_or_key)

    def keys(self):
        return self._fields


def main():

    spamm = Spamm(12, 'hello')
    print(dir(spamm))
    print(spamm._fields)
    d = dict(spamm)
    print(d)


if __name__ == '__main__':
    main()


with

Traceback (most recent call last):
  File "/home/neumann/test.py", line 4, in <module>
    class Spamm(NamedTuple):
RuntimeError: __class__ not set defining 'Spamm' as <class '__main__.Spamm'>. Was __classcell__ propagated to type.__new__?


The following works:

from typing import NamedTuple


def _getitem(instance, index_or_key):
    """Returns the respective item."""

    if isinstance(index_or_key, str):
        try:
            return getattr(instance, index_or_key)
        except AttributeError:
            raise IndexError(index_or_key) from None

    return super().__getitem__(index_or_key)


def dicttuple(cls: tuple):
    """Extends a tuple class with methods for the dict constructor."""

    cls.keys = lambda self: self._fields
    cls.__getitem__ = _getitem
    return cls


@dicttuple
class Spamm(NamedTuple):

    foo: int
    bar: str


def main():

    spamm = Spamm(12, 'hello')
    print(dir(spamm))
    print(spamm._fields)
    d = dict(spamm)
    print(d)


if __name__ == '__main__':
    main()


And produces:

['__add__', '__annotations__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__orig_bases__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_asdict', '_field_defaults', '_fields', '_make', '_replace', 'bar', 'count', 'foo', 'index', 'keys']
('foo', 'bar')
{'foo': 12, 'bar': 'hello'}


I am a bit baffled, why it works when the method is injected via a decorator.
History
Date User Action Args
2021-01-06 11:25:59conqpsetrecipients: + conqp, zzzeek, xtreak
2021-01-06 11:25:59conqpsetmessageid: <1609932359.6.0.384727611958.issue41629@roundup.psfhosted.org>
2021-01-06 11:25:59conqplinkissue41629 messages
2021-01-06 11:25:59conqpcreate