简体   繁体   English

从抽象基类继承时,为什么__slots__在Python 2和3中的行为不同

[英]Why is __slots__ behaving differently in Python 2 and 3 when inheriting from an abstract base class

I created the following class to store changeable points on a plane in a memory-efficient manner - I need a mutable equivalent of namedtuple('Point', 'x y') . 我创建了以下类,以节省内存的方式将可变点存储在平面上-我需要一个可变的namedtuple('Point', 'x y')等效项。 Since instance dictionaries are big, I thought I'd go for __slots__ : 由于实例字典很大,所以我认为我会去__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

When testing it on Python 3, everything seemed to be OK: 在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'

However on Python 2, I can set the attribute z even when it is not in slots: 但是,在Python 2上,即使不在插槽中,我也可以设置属性z

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

Why is that so, and why the difference between Python 2 and Python 3? 为什么会这样,为什么Python 2和Python 3之间会有区别?

The Python 2 data model says the following on __slots__ : Python 2数据模型在__slots__上表示以下内容:

  • When inheriting from a class without __slots__ , the __dict__ attribute of that class will always be accessible, so a __slots__ definition in the subclass is meaningless. 从没有__slots__的类继承时,该类的__dict__属性将始终可访问,因此子类中的__slots__定义是没有意义的。

And that is what is happening here. 这就是这里正在发生的事情。 In Python 2 the abstract base classes in the collections module did not have the __slots__ at all: 在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__'

This was reported as the issue 11333 in CPython issue tracker, and was fixed in Python 3.3. 在CPython问题跟踪器中,该问题被报告为问题11333 ,在Python 3.3中已修复。

In Python 3.3+ the Sequence abstract base class now has __slots__ set to an empty tuple: 在Python 3.3+中, Sequence抽象基类现在将__slots__设置为一个空元组:

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

Thus in Python 2, you cannot inherit from a collections base class and have memory efficient storage with __slots__ at the same time. 因此,在Python 2中,您不能从collections基类继承,并且不能同时具有__slots__内存高效存储。


Note however that even though the documentation on collections abstract base classes claims that 但是请注意,即使有关collections的文档抽象了基类,也声称

These ABCs allow us to ask classes or instances if they provide particular functionality, for example: 这些ABC允许我们询问类或实例是否提供特定功能,例如:

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

This is not the case with Sequence ; Sequence不是这种情况 simply implementing all the methods required by the Sequence does not make instances of your class to pass the isinstance check. 简单地实现Sequence所需的所有方法并不能使您的类的实例通过isinstance检查。

The reason for that is that the Sequence class does not have a __subclasshook__ ; 原因是Sequence类没有__subclasshook__ and in its absence, the parent class __subclasshook__ is consulted instead; 并且在没有它的情况下,将查询父类__subclasshook__ in this case Sized.__subclasshook__ ; 在这种情况下为Sized.__subclasshook__ ; and that returns NotImplemented if the tested-against class wasn't exactly Sized . 并且如果针对测试的类的Sized正确 ,则返回NotImplemented

On the other hand, one couldn't distinguish between a mapping type and a sequence type by the magic methods, as both of them can have the exactly same magic methods - of collections.OrderedDict has all the magic methods of a Sequence , including the __reversed__ method, yet it isn't a sequence. 另一方面,使用魔术方法无法区分映射类型和序列类型,因为它们都可以具有完全相同的魔术方法- collections.OrderedDict具有Sequence 所有魔术方法,包括__reversed__方法,但这不是序列。

However, you still do not need to inherit from Sequence to make isinstance(Point, Sequence) return True . 但是,您仍然不需要继承Sequence即可使isinstance(Point, Sequence)返回True In the following example, the Point is the same, except derived from object instead of Sequence , on Python 2: 在以下示例中,除了在Python 2上, Point是从object而不是Sequence派生的以外, Point是相同的:

>>> 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

You can register any class as a subclass of the abstract base class for the purpose of isinstance checks; 您可以将任何类注册为抽象基类的子类,以进行isinstance检查。 and of the extra mix-in methods, you really need to implement only count and index ; 在其他混合方法中,您实际上只需要实现countindex the functionality for others will be filled in by Python runtime. 其他功能将由Python运行时填充。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM