Did something about `namedtuple` change in 3.5.1?

2019-03-22 20:30发布

问题:

On Python 3.5.0:

>>> from collections import namedtuple
>>> cluster = namedtuple('Cluster', ['a', 'b'])
>>> c = cluster(a=4, b=9)
>>> c
Cluster(a=4, b=9)
>>> vars(c)
OrderedDict([('a', 4), ('b', 9)])

On Python 3.5.1:

>>> from collections import namedtuple
>>> cluster = namedtuple('Cluster', ['a', 'b'])
>>> c = cluster(a=4, b=9)
>>> c
Cluster(a=4, b=9)
>>> vars(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: vars() argument must have __dict__ attribute

Seems like something about namedtuple changed (or maybe it was something about vars()?).

Was this intentional? Are we not supposed to use this pattern for converting named tuples into dictionaries anymore?

回答1:

Per Python bug #24931:

[__dict__] disappeared because it was fundamentally broken in Python 3, so it had to be removed. Providing __dict__ broke subclassing and produced odd behaviors.

Revision that made the change

Specifically, subclasses without __slots__ defined would behave weirdly:

>>> Cluster = namedtuple('Cluster', 'x y')
>>> class Cluster2(Cluster):
    pass
>>> vars(Cluster(1,2))
OrderedDict([('x', 1), ('y', 2)])
>>> vars(Cluster2(1,2))
{}

Use ._asdict().



回答2:

From the docs

Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples.

The docs (and help(namedtuple)) say to use c._asdict() to convert to a dict.



回答3:

__dict__ was implemented as a @property and has been removed; you can see the change in the source code:

3.5.0:

def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + '({repr_fmt})' % self

@property
def __dict__(self):
    'A new OrderedDict mapping field names to their values'
    return OrderedDict(zip(self._fields, self))

def _asdict(self):
    'Return a new OrderedDict which maps field names to their values.'
    return self.__dict__

def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return tuple(self)

def __getstate__(self):
    'Exclude the OrderedDict from pickling'
    return None

3.5.1:

def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + '({repr_fmt})' % self

def _asdict(self):
    'Return a new OrderedDict which maps field names to their values.'
    return OrderedDict(zip(self._fields, self))

def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return tuple(self)