为什么 __slots__ 从抽象基础 class 继承时在 Python 2 和 3 中表现不同
Why is __slots__ behaving differently in Python 2 and 3 when inheriting from an abstract base class
我创建了以下 class 以节省内存的方式在平面上存储可变点 - 我需要 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 有区别?
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.
这就是这里发生的事情。在 Python 2 中,collections
模块中的抽象基础 classes 根本没有 __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 问题跟踪器中被报告为 issue 11333,并已在 Python 3.3 中修复。
在 Python 3.3+ 中,Sequence
抽象基础 class 现在 __slots__
设置为一个空元组:
>>> from collections import Sequence
>>> Sequence.__slots__
()
因此在 Python 2 中,您不能从 collections
基础 class 继承并同时具有 __slots__
的内存高效存储。
但是请注意,尽管 collections
abstract base classes 上的文档声称
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)
This is not the case with Sequence
;简单地实现 Sequence
所需的所有方法并不能使您的 class 实例通过 isinstance
检查。
原因是 Sequence
class does not have a __subclasshook__
; and in its absence, the parent class __subclasshook__
is consulted instead; in this case Sized.__subclasshook__
;并且 returns NotImplemented
如果针对 class 的测试不是 完全 Sized
.
另一方面,无法通过魔术方法区分映射类型和序列类型,因为它们都可以具有完全相同的魔术方法 - collections.OrderedDict
有 所有一个Sequence
的魔术方法,包括__reversed__
方法,但它不是一个序列。
但是,你仍然不需要继承Sequence
来制作isinstance(Point, Sequence)
returnTrue
。在下面的示例中,Point
是相同的,除了派生自 object
而不是 Sequence
,在 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
您可以将任何 class 注册为抽象基础 class 的子 class 以用于 isinstance
检查;在额外的混合方法中,你真的只需要实现 count
和 index
;其他人的功能将由 Python 运行时填充。
我创建了以下 class 以节省内存的方式在平面上存储可变点 - 我需要 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 有区别?
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.
这就是这里发生的事情。在 Python 2 中,collections
模块中的抽象基础 classes 根本没有 __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 问题跟踪器中被报告为 issue 11333,并已在 Python 3.3 中修复。
在 Python 3.3+ 中,Sequence
抽象基础 class 现在 __slots__
设置为一个空元组:
>>> from collections import Sequence
>>> Sequence.__slots__
()
因此在 Python 2 中,您不能从 collections
基础 class 继承并同时具有 __slots__
的内存高效存储。
但是请注意,尽管 collections
abstract base classes 上的文档声称
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)
This is not the case with Sequence
;简单地实现 Sequence
所需的所有方法并不能使您的 class 实例通过 isinstance
检查。
原因是 Sequence
class does not have a __subclasshook__
; and in its absence, the parent class __subclasshook__
is consulted instead; in this case Sized.__subclasshook__
;并且 returns NotImplemented
如果针对 class 的测试不是 完全 Sized
.
另一方面,无法通过魔术方法区分映射类型和序列类型,因为它们都可以具有完全相同的魔术方法 - collections.OrderedDict
有 所有一个Sequence
的魔术方法,包括__reversed__
方法,但它不是一个序列。
但是,你仍然不需要继承Sequence
来制作isinstance(Point, Sequence)
returnTrue
。在下面的示例中,Point
是相同的,除了派生自 object
而不是 Sequence
,在 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
您可以将任何 class 注册为抽象基础 class 的子 class 以用于 isinstance
检查;在额外的混合方法中,你真的只需要实现 count
和 index
;其他人的功能将由 Python 运行时填充。