Why __slots__ behaves differently in Python 2 and 3 when inheriting from an abstract base class

I created the following class for storing removable points on a plane in a memory-efficient manner - I need the mutable equivalent of namedtuple('Point', 'x y') . Since the dictionaries are large, I thought I would 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 

When testing in Python 3, everything seemed 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' 

However, in Python 2, I can set the z attribute even if it is not in the slots:

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

Why is this and why is the difference between Python 2 and Python 3?

+2
python abstract-base-class
Apr 04 '15 at 8:03
source share
1 answer

The Python 2 data model talks about __slots__ :

  • When inheriting from a class without __slots__ attribute of this class will always be available, so defining __slots__ in a subclass is pointless.

And here is what happens here. In Python 2, abstract base classes in the collections module did not have __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__' 

This was reported in issue 11333 in the problem tracking log on CPython and fixed in Python 3.3.

In Python 3.3+, the Sequence base class now has __slots__ for an empty tuple:

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



So in Python 2 you cannot inherit from the base class collections and at the same time have storage with memory with __slots__ .




Note that although the documentation for collections abstract base classes claims that

These ABCs allow us to define classes or instances if they provide specific functionality, for example:

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

This does not apply to Sequence ; simply executing all the methods required by Sequence does not make instances of your class to validate isinstance .

The reason for this is that the Sequence class does not have __subclasshook__ ; and in its absence, the parent class __subclasshook__ ; in this case, Sized.__subclasshook__ ; and returns NotImplemented if the NotImplemented class was not exactly Sized .

On the other hand, it was impossible to distinguish between display type and sequence type by magic methods, since both of them can have exactly the same magic methods - collections.OrderedDict have all the magic methods of a Sequence , including the __reversed__ method, but this is not a sequence.

However, you still don't need to inherit from Sequence to make isinstance(Point, Sequence) return True . In the following example, Point is the same as derived from object instead of Sequence in 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 

You can register any class as a subclass of an abstract base class for the purpose of checking isinstance ; and additional mixing methods, you really only need to implement count and index ; functionality for others will be populated with Python runtime.

+5
Apr 04 '15 at 8:03
source share



All Articles