[英]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
; 在其他混合方法中,您实际上只需要实现
count
和index
; the functionality for others will be filled in by Python runtime. 其他功能将由Python运行时填充。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.