Рубрики
Без рубрики

Неизменные объекты в Python

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

Чтобы программы были простыми, я стараюсь избегать побочных эффектов и стремлюсь к функциональному стилю программирования с использованием неизменных объектов. Я рад обменять несколько циклов процессора на снижение спроса на мощность мозга.

Потому что мы говорим о Python здесь и Мы все ответственные пользователи , невозможно создать актуальный объекты которые невозможно мутировать. Однако вы можете создавать вещи, которые ведут себя как объекты, которые невозможно мутировать, или фактические объекты, которые не могут быть мутированы по ошибке.

Давайте посмотрим на три способа сделать это и как они отличаются.

Названные кортежи

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

from collections import namedtuple


class Point(namedtuple("_Point", ["x", "y"])):
    def scale(self, scale):
        return Point(self.x * scale, self.y * scale)

    def translate(self, dx, dy):
        return Point(self.x + dx, self.y + dy)

Это класс для очков в двухмерном пространстве. Когда вы звоните масштаб или Перевод Метод возвращается новая точка. Этот вариант класса расширяет названный кортеж _Point состоящий из двух полей по имени x и у .

Когда вы попытаетесь мутировать экземпляр этого класса, вас встретят с ATTRIBUTERROR :

>>> from collections import namedtuple
>>> Point = namedtuple("_Point", ["x", "y"])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.x = 2
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: can't set attribute

Это очень похоже на неизменность для меня. Одним из недостатков этого подхода является то, что P не фактический объект. Это кортеж.

>>> SomethingCompletelyDifferent = namedtuple("SomethingCompletelyDifferent", "a b")
>>> a = SomethingCompletelyDifferent(1, 2)
>>> p == a
True
>>> p == (1, 2)
True

В зависимости от того, как вы используете экземпляры этого класса, это может быть большое дело. Документация для АТРТС Список пакетов Еще несколько недостатков Анкет

Атрис

Если вы не возражаете против зависимостей, вы можете использовать вышеупомянутые АТРТС Пакет и сделайте это:

import attr


@attr.s(frozen=True)
class Point:
    x = attr.ib()
    y = attr.ib()

    def scale(self, scale):
        return Point(self.x * scale, self.y * scale)

    def translate(self, dx, dy):
        return Point(self.x + dx, self.y + dy)

В этом случае декоратор @attr.s (frozen = true) диктует, что значения x и y не может быть изменено простыми заданиями. Это ведет себя так, как будто вы ожидаете:

>>> import attr
>>> @attr.s(frozen=True)
... class Point:
...     x = attr.ib()
...     y = attr.ib()
...
>>> p = Point(1, 2)
>>> p.x
1
>>> p.x = 2
Traceback (most recent call last):
  File "", line 1, in 
  File "/Users/lucengelen/.local/share/virtualenvs/python-immutable-1HIt_5XS/lib/python3.7/site-packages/attr/_make.py", line 428, in _frozen_setattrs
    raise FrozenInstanceError()
attr.exceptions.FrozenInstanceError
>>> p == (1, 2)
False
>>> p == Point(1, 2)
True
>>> p == Point(2, 1)
False

Вы все еще можете мутировать экземпляры этого класса, но не случайно:

>>> p = Point(1, 2)
>>> p.__dict__["x"] = 100
>>> p
Point(x=100, y=2)

Классы данных

Со времени Python 3.7, вы можете использовать классы данных Чтобы достичь чего -то похожего на вариант, используя атрис :

from dataclasses import dataclass


@dataclass(frozen=True)
class Point:
    x: int
    y: int

    def scale(self, scale):
        return Point(self.x * scale, self.y * scale)

    def translate(self, dx, dy):
        return Point(self.x + dx, self.y + dy)

Здесь декоратор @dataclass (замороженное = true) диктует, что значения x и y не может быть изменено простыми заданиями. Это также ведет себя так, как вы ожидаете:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True)
... class Point:
...     x: int
...     y: int
...
>>> p = Point(1, 2)
>>> p.x = 100
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'x'
>>> p = Point(1, 2)
>>> p == Point(1, 2)
True
>>> p == Point(2, 1)
False
>>> p == (1, 2)
False

Вы можете мутировать экземпляры так же, как и выше, но я не буду поверить вам, если скажем, вы сделали это по ошибке.

Вывод

Если вы хотите поиграть с этими вариантами, вы можете использовать оболочку Python. Вы также можете взглянуть на следующее репо: https://github.com/ljpengelen/immutable-python-objects Анкет

Мой личный вывод после рассмотрения этих вариантов заключается в том, что я пока не буду заменять все названные кортежи в существующих проектах. Я не ожидаю, что буду сгореть от неудачного поведения, касающегося равенства. Однако для будущих проектов я, вероятно, пойду с классами данных.

Оригинал: “https://dev.to/kabisasoftware/immutable-objects-in-python-2g0f”