从抽象基类继承时,为什么__slots__在Python 2和3中的行为不同? [英] Why is __slots__ behaving differently in Python 2 and 3 when inheriting from an abstract base class
问题描述
我创建了以下类,以节省内存的方式将可变点存储在平面上-我需要一个可变的namedtuple('Point', 'x y')
等效项.由于实例字典很大,所以我认为我会选择__slots__
:
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')
. Since instance dictionaries are big, I thought I'd go for __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上进行测试时,一切似乎都没问题:
When testing it on Python 3, everything seemed to be OK:
>>> 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
:
However on Python 2, I can set the attribute z
even when it is not in slots:
>>> pt = Point(12, 42)
>>> pt.z = 5
>>> pt.z
5
>>> pt.__slots__
('x', 'y')
>>> pt.__dict__
{'z': 5}
为什么会这样,为什么Python 2和Python 3之间会有区别?
Why is that so, and why the difference between Python 2 and Python 3?
推荐答案
The Python 2 data model says the following on __slots__
:
- 从没有
__slots__
的类继承时,该类的__dict__
属性将始终可访问,因此子类中的__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.
这就是这里正在发生的事情.在Python 2中,collections
模块中的抽象基类根本没有__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:
>>> 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问题跟踪程序中,它被报告为 issue 11333 ,并已在Python 3.3中修复.
This was reported as the issue 11333 in CPython issue tracker, and was fixed in Python 3.3.
在Python 3.3+中,Sequence
抽象基类现在将__slots__
设置为一个空元组:
In Python 3.3+ the Sequence
abstract base class now has __slots__
set to an empty tuple:
>>> from collections import Sequence
>>> Sequence.__slots__
()
因此,在Python 2中,您不能从collections
基类继承,并且不能同时与__slots__
具有内存有效存储.
Thus in Python 2, you cannot inherit from a collections
base class and have memory efficient storage with __slots__
at the same time.
但是请注意,即使 collections
抽象基类的文档声称
Note however that even though the documentation on collections
abstract base classes claims that
这些ABC允许我们询问类或实例是否提供特定功能,例如:
These ABCs allow us to ask classes or instances if they provide particular functionality, for example:
size = None
if isinstance(myvar, collections.Sized):
size = len(myvar)
Sequence
并非如此;简单地实现Sequence
所需的所有方法并不能使您的类的实例通过isinstance
检查.
This is not the case with Sequence
; simply implementing all the methods required by the Sequence
does not make instances of your class to pass the isinstance
check.
原因是 Sequence
类没有 __subclasshook__
;并且在没有它的情况下,将查询父类__subclasshook__
;在这种情况下, Sized.__subclasshook__
;如果针对测试的类不是完全 Sized
,则返回NotImplemented
.
The reason for that is that the Sequence
class does not have a __subclasshook__
; and in its absence, the parent class __subclasshook__
is consulted instead; in this case Sized.__subclasshook__
; and that returns NotImplemented
if the tested-against class wasn't exactly Sized
.
另一方面,魔术方法无法区分映射类型和序列类型,因为它们都可以具有完全相同的魔术方法-collections.OrderedDict
的 all Sequence
的魔术方法,包括__reversed__
方法,但它不是序列.
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.
但是,您仍然不需要继承Sequence
即可使isinstance(Point, Sequence)
返回True
.在下面的示例中,Point
相同,除了在Python 2上从object
而不是Sequence
派生
However, you still do not need to inherit from Sequence
to make isinstance(Point, Sequence)
return True
. In the following example, the Point
is the same, except derived from object
instead of Sequence
, on Python 2:
>>> 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
检查.在其他混合方法中,您实际上只需要实现count
和index
;其他功能将由Python运行时填充.
You can register any class as a subclass of the abstract base class for the purpose of isinstance
checks; and of the extra mix-in methods, you really need to implement only count
and index
; the functionality for others will be filled in by Python runtime.
这篇关于从抽象基类继承时,为什么__slots__在Python 2和3中的行为不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!