有没有办法用 collections.namedtuple 支持 weakrefs?

Is there a way to support weakrefs with collections.namedtuple?

我想对命名元组使用弱引用,但失败了:

>>> import collections
>>> import weakref
>>>
>>> Foo = collections.namedtuple('Foo','a b c')
>>> weakref.ref(Foo(1,2,3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'Foo' object

我尝试用 __slots__ = ('__weakref__',) 解决这个问题,但也失败了:

>>> class Foo(collections.namedtuple('Foo','a b c')):
...     __slots__ = ('__weakref__',)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    nonempty __slots__ not supported for subtype of 'Foo'

有解决办法吗?

简答

不幸的是,非空 __slots__ 完全不适用于任何类型的 元组 ,而不仅仅是namedtuple().

解决方法

你能做的最好的事情就是拥有一个不继承自 tuple 的 class。

我为你写了一个(见下文)。它是这样使用的:

from __future__ import print_function
from altnamedtuple import AltNamedTuple
from weakref import proxy

class Point(AltNamedTuple):
    __slots__ = ('x', 'y', '__weakref__')
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Now, exercise the capabilities of the named tuple alternative
p = Point(10, 20)
r = proxy(p)
print(len(r))               # sizeable
print(r[0])                 # indexed access
print(r.y)                  # attribute access
print(list(r))              # iterable
x, y = r                    # unpackable
print(x, y)
print(20 in r)              # membership testing
print(tuple(reversed(p)))   # reversible
print(r == (10, 20))        # equality
print(r != (30, 40))        # inequality
print(hash(p))              # hashable
print(r)                    # nice repr
print(r._asdict())          # conversion to a dict
print(r._replace(y=2))
t = (11, 22)
print(r.count(10))
print(r.index(20))
print(Point._make(t))       # alternative constructor

请注意,weakrefs 代理不会通过对 __hash____reversed__ 的调用传递。这是任何 class 都无法解决的内在限制。

AltNamedTuple 的源代码

这是完成所有工作的 class:

class AltNamedTuple(object):
    "Subclasser needs to define:  __slots__ and __init__"
    __slots__ = ()
    def __getattr__(self, attr):
        if attr != '_fields': raise AttributeError(attr)
        if '__weakref__' in self.__slots__:
            return self.__slots__[:-1]
        return self.__slots__
    def __len__(self):
        return len(self._fields)
    def __getitem__(self, index):
        attr = self._fields[index]
        return getattr(self, attr)
    def __iter__(self):
        for attr in self._fields:
            yield getattr(self, attr)
    def __reversed__(self):
        return iter(reversed(tuple(self)))
    def __eq__(self, other):
        return tuple(self) == tuple(other)
    def __ne__(self, other):
        return tuple(self) != tuple(other)
    def __hash__(self):
        return hash(tuple(self))
    def __repr__(self):
        pairs = ['%s=%r' % (a, getattr(self, a)) for a in self._fields]
        return ('%s(%s)' % (self.__class__.__name__, ', '.join(pairs)))
    @classmethod
    def _make(cls, iterable):
        return cls(*iterable)
    def _asdict(self):
        return dict(zip(self._fields, self))
    def _replace(_self, **kwds):
        d = _self._asdict()
        d.update(kwds)
        return _self.__class__(**d)
    def count(self, value):
        return tuple(self).count(value)
    def index(self, value):
        return tuple(self).index(value)