Сообщить об ошибке.

Настройка классов Python магическими методами

В разделе рассмотрена базовая пользовательская настройка классов Python специальными (магическими) методами. Предназначение методов, а так же поведение при их вызове.

Содержание:


object.__new__(cls[, ...]):

Метод object.__new__() вызывается для создания нового экземпляра класса cls.__new__() - это статический метод (в специальном регистре, поэтому не нужно объявлять его как таковой), который принимает в качестве первого аргумента класс cls, экземпляр которого был запрошен. Остальные аргументы передаются в выражение конструктора объекта при вызове класса. Возвращаемое значение метода __new__() должно быть новым экземпляром объекта (обычно экземпляром cls).

Типичные реализации создают новый экземпляр класса, вызывая метод суперкласса __new__() с использованием super().__new__(cls [, ...]) с соответствующими аргументами, а затем при необходимости изменяя вновь созданный экземпляр перед его возвратом.

Если __new__() вызывается во время создания объекта и возвращает экземпляр или подкласс cls, тогда метод __init__() нового экземпляра будет вызываться как __init__(self [, ...]), где self - это новый экземпляр, а остальные аргументы те же, что были переданы конструктору объекта.

Если __new__() не возвращает экземпляр cls, то метод __init__() нового экземпляра не вызывается.

Специальный метод __new__() предназначен в основном для того, чтобы позволить подклассам неизменяемых типов (например, int, str или tuple) настраивать создание экземпляров. Он также обычно переопределяется в настраиваемых метаклассах, для настройки создания класса.

object.__init__(self[, ...]):

Метод object.__init__() представляет собой конструктор класса и вызывается после создания экземпляра (с помощью __new__()), но до того, как он будет возвращен вызывающей стороне. Аргументы передаются в выражение конструктора класса.

Если базовый класс имеет метод __init__(), то метод производного класса __init__(), если таковой имеется, должен явно вызывать его, чтобы обеспечить правильную инициализацию части экземпляра базового класса; например: super().__init__([args ...]).

Поскольку магические методы __new__() и __init__() работают вместе при создании объектов (__new__() для его создания, а __init__() для его настройки), то метод __init__() не может возвращать значения, отличные от None. Это приведет к возникновению ошибки TypeError во время выполнения.

class Order:
    # конструктор класса
    def __init__(self, price, num):
        self.price = price
        self.num = num
    
    def __getattr__(self, attrname):
        if attrname == "total":
            return self.price * self.num

>>> order = Order(10, 15)
>>> order.price
# 10
>>> order.num
# 15
>>> order.total
# 150

object.__del__(self):

Метод object.__del__() вызывается, когда экземпляр класса будет уничтожен. Это также называется финализатором или (неправильно) деструктором.

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

Возможно (хотя и не рекомендуется!) метод __del__() может откладывать уничтожение экземпляра, создав на него новую ссылку. Это называется воскрешением объекта. Такое поведение зависит от реализации, может ли __del__() вызываться второй раз, когда воскресший объект должен быть уничтожен. Текущая реализация CPython вызывает его только один раз.

Не гарантируется, что методы __del__() будут вызваны для объектов, которые все еще существуют, когда интерпретатор завершает работу.

Обратите внимание, что выражение del x не вызывает напрямую x.__del__() - первый уменьшает счетчик ссылок для x на единицу, а второй вызывается только тогда, когда счетчик ссылок x достигает нуля.

Детали реализации CPython: цикл ссылок может предотвратить обнуление счетчика ссылок объекта. В этом случае цикл будет позже обнаружен и удален циклическим сборщиком мусора. Частая причина циклов обращения - это когда исключение было перехвачено в локальной переменной. Затем локальные переменные кадра ссылаются на исключение, которое ссылается на собственную трассировку, которая ссылается на локальные переменные всех кадров, перехваченных трассировкой.

Предупреждение. Из-за сомнительных обстоятельств, при которых вызываются методы __del__(), исключения, возникающие во время их выполнения, игнорируются, а вместо них в sys.stderr выводится предупреждение.

В частности:

  • специальный метод __del__() может быть вызван при выполнении произвольного кода, в том числе из любого произвольного потока. Если __del__() необходимо принять блокировку или вызвать любой другой блокирующий ресурс, он может заблокироваться, поскольку ресурс уже может быть занят кодом, который прерывается для выполнения __del__().

  • метод __del__() может вызываться во время завершения работы интерпретатора. Как следствие, глобальные переменные, к которым он должен получить доступ (включая другие модули), возможно, уже были удалены или установлены на None. Python гарантирует, что глобальные объекты, имя которых начинается с одного символа подчеркивания, удаляются из своего модуля до того, как будут удалены другие глобальные объекты. Если других ссылок на такие глобальные объекты не существует, это может помочь гарантировать, что импортированные модули все еще доступны во время вызова метода __del__().

object.__repr__(self):

Метод object.__repr__() вызывается встроенной функцией repr() для вычисления официального строкового представления объекта. Если это вообще возможно, то это должно выглядеть как допустимое выражение Python, которое можно было бы использовать для воссоздания объекта с тем же значением (в соответствующей среде). Если это невозможно, то должна быть возвращена строка формы <...some useful description...> (какое-то полезное описание). Возвращаемое значение должно быть строковым объектом. Если класс определяет специальный метод __repr__(), но не __str__(), то тогда __repr__() также используется, когда требуется неформальное строковое представление экземпляров этого класса.

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

class Order:
    def __init__(self, price, num):
        self.price = price
        self.num = num
    
    def __getattr__(self, attrname):
        if attrname == "total":
            return self.price * self.num

    # официальное строковое представление
    def __repr__(self):
        return f'{self.__class__.__name__}(price={self.price},\
                num={self.num}, total={self.total})'

>>> order = Order(10, 15)
>>> repr(order)
# 'Order(price=10, num=15, total=150)'

object.__str__(self):

Метод object.__str__() вызывается встроенной функцией str() и встроенными функциями format() и print() для вычисления неформального или красиво печатаемого строкового представления объекта. Возвращаемое значение должно быть строковым объектом.

Этот метод отличается от специального метода object.__repr__() тем, что не ожидается, что __str__() вернет допустимое выражение Python: можно использовать более удобное или краткое представление.

Реализация по умолчанию, определяемая объектом встроенного типа, вызывает object.__repr__().

class Order:
    def __init__(self, price, num):
        self.price = price
        self.num = num
    
    def __getattr__(self, attrname):
        if attrname == "total":
            return self.price * self.num

    # неформальное строковое представление
    def __str__(self):
        return f'price={self.price}, num={self.num}, total={self.total}'

>>> order = Order(10, 15)
>>> str(order)
# 'price=10, num=15, total=150'

object.__bytes__(self):

Метод object.__bytes__() вызывается встроенной функцией bytes() для вычисления представления объекта в виде байтовой строки. Возвращаемое значение должно быть байтовым объектом.

object.__format__(self, format_spec):

Метод object.__format__() вызывается встроенной функцией format() и, по расширению, вычислением форматированных строковых литералов и методом str.format() для создания форматированного строкового представления объекта.

Аргумент format_spec - это строка, содержащая описание желаемых параметров форматирования. Интерпретация аргумента format_spec зависит от типа, реализующего специальный метод __format__(), но большинство классов либо делегируют форматирование одному из встроенных типов, либо используют аналогичный синтаксис параметра форматирования.

Описание стандартного синтаксиса форматирования смотрите в документации к функцией format().

Возвращаемое значение должно быть строковым объектом.

Изменено в версии 3.7: object.__format__(x, '') теперь эквивалентен str(x), а не format(str(x), '').

object.__lt__(self, other),
object.__le__(self, other),
object.__eq__(self, other),
object.__ne__(self, other),
object.__gt__(self, other),
object.__ge__(self, other):

Перечисленные методы - называются методами расширенного сравнения.

Соответствие между символами операторов и именами методов следующее:

  • x < y: вызывает x.__lt__(y),
  • x <= y: вызывает x.__le__(y),
  • x == y вызывает x.__eq__(y),
  • x != y вызывает x.__ne__(y),
  • x > y вызывает x.__gt__(y),
  • x >= y вызывает x.__ge__(y).

Методы расширенного сравнения могут возвращать одиночный объект NotImplemented, если он не реализует операцию для данной пары аргументов. По соглашению, для успешного сравнения возвращаются False и True. Однако эти методы могут возвращать любое значение, поэтому, если оператор сравнения используется в логическом контексте (например, в условии оператора if), Python для значения вызовет функцию bool(), чтобы определить, является ли результат истинным или ложным.

По умолчанию объект реализует __eq__() с помощью оператора is, возвращая NotImplemented в случае ложного сравнения: True, если x идентично y, иначе NotImplemented. Метод __ne__() по умолчанию делегирует __eq__() и инвертирует результат, если он не NotImplemented. Других подразумеваемых отношений между операторами сравнения или реализациями по умолчанию нет; например, истинность (x<y or x==y) не означает x<=y. Чтобы автоматически генерировать операции упорядочивания из одной корневой операции, смотрите functools.total_ordering() ().

Смотрите описание метода __hash__() для некоторых важных замечаний по созданию хешируемых объектов, которые поддерживают настраиваемые операции сравнения и могут использоваться как ключи словаря.

У этих специальных методов не существует версий с заменяемыми аргументами (для использования, когда левый аргумент не поддерживает операцию, а правый аргумент поддерживает). Скорее, метод __lt__() это отражение __gt__() и наоборот, __le__() это отражение __ge__() и наоборот, а __eq__() и __ne__() - их собственное отражение. Если операнды имеют разные типы, а тип правого операнда является прямым или косвенным подклассом типа левого операнда, то отраженный метод правого операнда имеет приоритет, в противном случае метод левого операнда имеет приоритет. Виртуальный подкласс не рассматривается.

object.__hash__(self):

Метод object.__hash__() вызывается встроенной функцией hash() и для операций с элементами хешированных коллекций, включая set, frozenset и dict. Специальный метод __hash__() должен возвращать целое число. Единственное обязательное свойство - это то, что сравниваемые одинаковые объекты имеют одинаковое хеш-значение. Рекомендуется смешивать вместе хеш-значения компонентов объекта, которые также играют роль в сравнении объектов, упаковывая их в кортеж и вычислять хэш значение кортежа.

Пример:

def __hash__(self):
    return hash((self.name, self.nick, self.color))

Обратите внимание, что функция hash() обрезает значение, возвращаемое пользовательским методом __hash__() объекта, до размера Py_ssize_t. Обычно это 8 байтов для 64-разрядных сборок и 4 байта для 32-разрядных сборок. Если __hash__() объекта должен взаимодействовать со сборками с разным размером бит, то обязательно проверьте это во всех поддерживаемых сборках. Простой способ сделать это - использовать python -c 'import sys; print(sys.hash_info.width)'.

Если класс не определяет метод __eq__(), то он также не должен определять метод __hash__(). Если класс определяет __eq__(), но не определяет __hash__(), то его экземпляры не будут использоваться как элементы в хэшируемых коллекциях. Если класс определяет изменяемые объекты и реализует метод __eq__(), то он не должен реализовывать метод __hash__(), поскольку реализация хешируемых коллекций требует, чтобы хеш-значение ключа было неизменным (если хеш-значение объекта изменится, то хеш будет неверным).

Пользовательские классы по умолчанию имеют методы __eq__() и __hash__(). С ними все объекты сравниваются как неравные кроме самих себя, а x.__hash__() возвращает соответствующее значение, так что x == y подразумевает, что x is y и hash(x) == hash(y).

Класс, который переопределяет __eq__() и не определяет __hash__(), будет иметь для своего метода __hash__() неявное значение None. Если для метода класса __hash__() установлено значение None, то экземпляры класса вызовут соответствующую ошибку TypeError, когда программа попытается получить их хеш-значение, а также будут правильно идентифицированы как нехешируемые при проверке isinstance(obj, collections.abc.Hashable).

Если класс, который переопределяет __eq__() и вместе с этим должен сохранить реализацию __hash__() из родительского класса, то интерпретатору об этом необходимо сообщить явно, установив __hash__ = ParentClass.__hash__.

Если класс, который не переопределяет __eq__() и желает подавить поддержку хэша, то он должен включить в определение класса __hash__ = None. Класс, который определяет свой собственный __hash__(), который явно вызывает TypeError, будет неправильно идентифицирован как хешируемый при вызове isinstance(obj, collections.abc.Hashable).

Примечание. По умолчанию значения метода __hash__() объектов str и bytes подсолены непредсказуемым случайным значением. Хотя они остаются постоянными в рамках отдельного процесса Python, их нельзя предсказать между повторными вызовами Python.

Это предназначено для обеспечения защиты от отказа в обслуживании, вызванного тщательно подобранными входными данными, которые используют наихудшую производительность вставки dict, сложность O(n^2).

Изменение значений хеш-функции влияет на порядок итерации множества. Python никогда не давал гарантий относительно этого порядка и обычно он по разному варьируется между 32-битными и 64-битными сборками.

Смотрите также описание переменной окружения интерпретатора PYTHONHASHSEED.

object.__bool__(self):

Метод object.__bool__() вызывается для реализации проверки истинности и встроенной функцией bool(). Метод должен вернуть False или True.

Когда этот магический метод не определен, то вызывается специальный метод __len__(), если он определен, и объект считается истинным, если его результат не равен нулю. Если класс не определяет ни __len__(), ни __bool__(), то все его экземпляры считаются истинными.