__slots__ в Python — примеры использования

В этом руководстве вы узнаете о __slots__ в Python и о том, как их использовать, чтобы сделать ваш класс более эффективным.

Содержание

Что такое __slots__ в Python?

Ниже определяется класс Point2D, имеющий два атрибута, включая координаты x и y:

class Point2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point2D({self.x},{self.y})'

Каждый экземпляр класса Point2D имеет свой собственный атрибут __dict__, который хранит атрибуты экземпляра. Например:

point = Point2D(0, 0)
print(point.__dict__)

По умолчанию Python использует словари для управления атрибутами экземпляра. Словарь позволяет динамически добавлять атрибуты к экземпляру во время выполнения. Однако он также имеет определенные накладные расходы памяти. Если класс Point2D имеет много объектов, то будут большие накладные расходы памяти.

Чтобы избежать накладных расходов памяти, Python ввел слоты. Если класс содержит только фиксированные (или предопределенные) атрибуты экземпляра, вы можете использовать слоты, чтобы указать Python использовать более компактную структуру данных вместо словарей.

Например, если класс Point2D имеет только два атрибута экземпляра, вы можете указать атрибуты в слотах следующим образом:

class Point2D:
    __slots__ =('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point2D({self.x},{self.y})'

В этом примере вы назначаете итерируемый объект (кортеж), содержащий имена атрибутов, которые вы будете использовать в классе.

Сделав это, Python не будет использовать __dict__ для экземпляров класса. Следующее вызовет ошибку AttributeError:

point = Point2D(0, 0)
print(point.__dict__)

Ошибка:

AttributeError: 'Point2D' object has no attribute __dict__

Вместо этого вы увидите __slots__ в экземпляре класса. Например:

point = Point2D(0, 0)
print(point.__slots__)

Выход:

('x', 'y')

Кроме того, вы не можете добавлять больше атрибутов к экземпляру динамически во время выполнения. Следующее приведет к ошибке:

point.z = 0

Ошибка:

AttributeError: 'Point2D' object has no attribute 'z'

Однако вы можете добавить к классу атрибуты класса:

Point2D.color = 'black'
pprint(Point2D.__dict__)

Выход:

mappingproxy({'__doc__': None,
              '__init__': <function Point2D.__init__ at 0x000001BBBA841310>,
              '__module__': '__main__',
              '__repr__': <function Point2D.__repr__ at 0x000001BBBA8413A0>,
              '__slots__':('x', 'y'),
              'color': 'black',
              'x': <member 'x' of 'Point2D' objects>,
              'y': <member 'y' of 'Point2D' objects>})

Этот код работает, потому что Python применяет слоты к экземплярам класса, а не к самому классу.

Python __slots__ и одиночное наследование

Давайте рассмотрим слоты в контексте наследования.

Базовый класс использует слоты, а подкласс — нет

Ниже определяется Point2D как базовый класс, а Point3D как подкласс, наследуемый от класса Point2D:

class Point2D:
    __slots__ =('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point2D({self.x},{self.y})'


class Point3D(Point2D):
    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z


if __name__ == '__main__':
    point = Point3D(10, 20, 30)
    print(point.__dict__)

Выход:

{'z': 30}

Класс Point3D не имеет слотов, поэтому его экземпляр имеет атрибут __dict__. В этом случае подкласс Point3D использует слоты из своего базового класса (если они доступны) и использует словарь экземпляров.

Если вы хотите, чтобы класс Point3D использовал слоты, вы можете определить дополнительные атрибуты следующим образом:

class Point3D(Point2D):
    __slots__ =('z',)

    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z

Обратите внимание, что вы не указываете атрибуты, которые уже указаны в __slots__ базового класса.

Теперь класс Point3D будет использовать слоты для всех атрибутов, включая x, y и z.

Базовый класс не использует __slots__, и подкласс не использует

В следующем примере определяется базовый класс, который не использует __slots__, а подкласс использует:

class Shape:
    pass


class Point2D(Shape):
    __slots__ =('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y


if __name__ == '__main__':
    # use both slots and dict to store instance attributes
    point = Point2D(10, 10)
    print(point.__slots__)
    print(point.__dict__)

    # can add the attribute at runtime
    point.color = 'black'
    print(point.__dict__)

Выход:

('x', 'y')
{'color': 'black'}

В этом случае экземпляры класса Point2D используют как __slots__, так и словарь для хранения атрибутов экземпляра.

Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *