2

我创建了以下类,以一种内存有效的方式存储平面上的可变点 - 我需要一个可变的namedtuple('Point', 'x y'). 由于实例字典很大,我想我会去__slots__

from collections import Sequence

class Point(Sequence):
    __slots__ = ('x', 'y')

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

    def __repr__(self):
        return 'Point(x=%r, y=%r)' % (self.x, self.y)

    def __len__(self):
        return 2

在 Python 3 上对其进行测试时,一切似乎都很好:

>>> pt = Point(12, 42)
>>> pt[0], pt.y
(12, 42)
>>> pt.x = 5
>>> pt
Point(x=5, y=42)
>>> pt.z = 6
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'

但是在 Python 2 上,z即使它不在插槽中,我也可以设置属性:

>>> pt = Point(12, 42)
>>> pt.z = 5
>>> pt.z
5
>>> pt.__slots__
('x', 'y')
>>> pt.__dict__
{'z': 5}

为什么会这样,为什么 Python 2 和 Python 3 之间存在差异?

4

1 回答 1

6

Python 2 数据模型在__slots__:

  • 从没有 的类继承时,该类__slots____dict__属性将始终可访问,因此__slots__子类中的定义是没有意义的。

这就是这里正在发生的事情。在 Python 2 中,collections模块中的抽象基类根本没有__slots__

>>> from collections import Sequence
>>> Sequence.__slots__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Sequence' has no attribute '__slots__'

这在 CPython 问题跟踪器中报告为问题 11333,并在 Python 3.3 中得到修复。

在 Python 3.3+ 中,Sequence抽象基类现在__slots__设置为空元组:

>>> from collections import Sequence
>>> Sequence.__slots__
()

因此,在 Python 2 中,您不能从collections基类继承并同时拥有高效的内存存储__slots__


但是请注意,即使关于collections抽象基类的文档声称

这些 ABC 允许我们询问类或实例是否提供特定功能,例如:

size = None
if isinstance(myvar, collections.Sized):
    size = len(myvar)

情况并非如此Sequence;简单地实现所需的所有方法Sequence不会使您的类的实例通过isinstance检查。

原因是Sequence该类没有__subclasshook__; __subclasshook__并且在它不存在的情况下,改为咨询父类;在这种情况下Sized.__subclasshook__;如果NotImplemented经过测试的类不完全是 Sized.

另一方面,不能通过魔术方法区分映射类型和序列类型,因为它们都可以具有完全相同的魔术方法 - ofcollections.OrderedDict具有a 的所有魔术方法Sequence,包括__reversed__方法,但它不是一个序列。

但是,您仍然不需要继承 fromSequence来制作isinstance(Point, Sequence)return True。在以下示例中Point,除了派生自Python 2 上的object而不是之外, 是相同的:Sequence

>>> pt = Point(12, 42)
>>> pt.z = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> isinstance(pt, Sequence)
False
>>> Sequence.register(pt)
>>> isinstance(pt, Sequence)
True

您可以将任何类注册为抽象基类的子类以进行isinstance检查;和额外的混合方法,你真的只需要实现countand index; 其他人的功能将由 Python 运行时填充。

于 2015-04-04T08:03:56.853 回答