Почему __slots__ ведет себя по-другому в Python 2 и 3 при наследовании от абстрактного базового класса

Я создал следующий класс для хранения сменных точек на плоскости эффективным с точки зрения памяти образом – мне нужен изменяемый эквивалент 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__ :

    • При наследовании от класса без __slots__ атрибут __dict__ этого класса всегда будет доступен, поэтому определение __slots__ в подклассе бессмысленно.

    И вот что здесь происходит. В 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__' 

    Об этом сообщается в выпуске 11333 в журнале отслеживания проблем CPython и исправлена ​​в Python 3.3.

    В Python 3.3+ базовый класс Sequence теперь имеет __slots__ установленный в пустой кортеж:

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

    Таким образом, в Python 2 вы не можете наследовать из базового класса collections и одновременно иметь хранилище с __slots__ .


    Обратите внимание, однако, что хотя документация по collections абстрактных базовых классов утверждает, что

    Эти ABC позволяют нам задавать классы или экземпляры, если они обеспечивают определенную функциональность, например:

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

    Это не относится к Sequence ; простое выполнение всех методов, требуемых Sequence , не делает экземпляры вашего класса для передачи проверки isinstance .

    Причина этого в том, что класс Sequence не имеет __subclasshook__ ; и при его отсутствии рассматривается родительский класс __subclasshook__ ; в этом случае Sized.__subclasshook__ ; и который возвращает NotImplemented если класс с NotImplemented не был точно определен .

    С другой стороны, нельзя было различать тип отображения и тип последовательности магическими методами, так как оба они могут иметь точно такие же магические методы – collections.OrderedDict есть все магические методы Sequence , включая __reversed__ , но это не последовательность.

    Однако вам по-прежнему не нужно наследовать от Sequence чтобы сделать isinstance(Point, Sequence) возвратом True . В следующем примере 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 

    Вы можете зарегистрировать любой класс в качестве подкласса абстрактного базового класса с целью проверки isinstance ; и дополнительных методов смешивания, вам действительно нужно реализовать только count и index ; функциональность для других будет заполнена временем выполнения Python.