Pergunta

I have a tiny class that extends a namedtuple, but the __dict__ property of its instances is always returning empty.

Point = namedtuple('Point', 'x y')
p1 = Point(20, 15)
print(p1, p1.__dict__)
# Point(x=20, y=15) OrderedDict([('x', 20), ('y', 15)]) <--- ok

class SubPoint(Point): pass
p2 = SubPoint(20, 15)
print(p2, p2.__dict__)
# SubPoint(x=20, y=15) {} <--- why is it empty?

p2 has the attributes, but its __dict__ is empty. They are listed correctly with dir(), though, which is strange. Note this work correctly when SubPoint extends a vanilla class.

What is happening, and how do I list the attributes in my subclass instance?

Foi útil?

Solução 2

To see why __dict__ doesn't work, check the answer by Ashwini Chaudhary. This answer covers the second part of the question (how to list namedtuple attributes).

To list namedtuple attributes, there are _fields and _asdict:

>>> import collections as c
>>> Point = c.namedtuple("Point", ["x", "y"])
>>> p1 = Point(20, 15)
>>> print(p1._fields)
('x', 'y')
>>> print(p1._asdict())
{'x': 20, 'y': 15}
>>> class SubPoint(Point): pass
...
>>> p2 = SubPoint(20, 15)
>>> print(p2._fields)
('x', 'y')
>>> print(p2._asdict())
{'x': 20, 'y': 15}

Note that _fields is defined on the class, so you can also do:

>>> print(SubPoint._fields)
('x', 'y')

Obviously, _asdict needs an instance so it can use the values.

I used Python 3.9.7 for the examples, I'm not exactly sure when this stuff was added (maybe someone who knows can comment).

Outras dicas

The problem is that __slots__ is only limited to a class it is defined in, so base classes will always have their own __dict__ attribute unless you define __slots__ there too. (And also note that the __dict__ attribute of namedtuple is not a normal dict but a @property.)

From docs:

The action of a __slots__ declaration is limited to the class where it is defined. As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).

So, when you defined __slots__ in the subclass then it failed to look for an attribute __dict__ in that class, so moved on to base class where it found the __dict__ attribute.

A simple demo:

class A:
    __slots__=  ('a', 'b')

    @property
    def __dict__(self):
        print ('inside A')
        return self.__slots__         

class B(A):
    pass

print(B().__dict__)

print ('-'*20)

class B(A):
    __slots__ = ()
print(B().__dict__)

output:

{}
--------------------
inside A
()

Declaring __slots__ = () in the subclass fixed the problem, but the reason is not clear. As far as I know __slots__ should not change the behavior of code in such subtle ways, so this is not a complete answer.

Point = namedtuple('Point', 'x y')
p1 = Point(20, 15)
print(p1, p1.__dict__)
# Point(x=20, y=15) OrderedDict([('x', 20), ('y', 15)]) <--- ok

class SubPoint(Point):
    __slots__ = ()
p2 = SubPoint(20, 15)
print(p2, p2.__dict__)
# SubPoint(x=20, y=15) OrderedDict([('x', 20), ('y', 15)]) <--- fixed

An answer with an explanation is welcome.

list(A.__annotations__.keys())
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top