Почему Python использует "магические методы"?

Я недавно играл с Python, и одна вещь, которую я нахожу немного странной, - это широкое использование "магических методов", например, чтобы сделать его длину доступной, объект реализует метод,def __len__(self), и тогда он вызывается, когда вы пишете len(obj).

мне просто интересно, почему объекты не просто определить len(self) метод и вызывается непосредственно как член объекта, например obj.len()? Я уверен, что у Python должны быть веские причины делать это так, как он это делает, но как новичок, я еще не понял, что это такое.

7 ответов


AFAIK,len особенная в этом отношении и имеет исторические корни.

вот цитата:из FAQ:

почему Python использует методы для некоторых функциональность (например, список.индекс ()), но функции для других (например, LEN (list))?

основная причина-история. Функции для тех операций, которые общие для группы типов и которые должны были работать даже для возражает, что не было методов в все (например, кортежи). Также удобно иметь функцию, которая может легко наносится на аморфный коллекция объектов при использовании функциональные возможности Python (карта(), применить() и др.).

фактически, реализуя len (), max(), min () как встроенная функция фактически меньше кода, чем реализация их как методы для каждого типа. Можно придирайтесь к отдельным случаям, но это в Python, и это тоже поздно делать такое основополагающие изменения сейчас. Функции должны оставаться избегайте массивного обрыва кода.

другие "магические методы" (на самом деле называется специальный метод в фольклоре Python) имеют большой смысл, и аналогичная функциональность существует на других языках. Они в основном используются для кода, который вызывается неявно при использовании специального синтаксиса.

например:

  • перегруженные операторы (существуют в C++ и др.)
  • конструктор/деструктор
  • крючки для доступа к атрибутам
  • инструменты метапрограммирования

и так далее...


из Дзэн питона:

перед лицом двусмысленности, откажитесь от искушения угадать.
Должен быть один-и предпочтительно только один-очевидный способ сделать это.

это одна из причин-с помощью пользовательских методов разработчики могут свободно выбирать другое имя метода, например getLength(), length(), getlength() или вообще. Python применяет строгое именование, чтобы общая функция len() можно использовать.

все операции, которые являются общими для многих типов объектов магические методы, как __nonzero__, __len__ или __repr__. Однако они в основном необязательны.

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


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

рассмотрим следующий пример:

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Это дает ошибку, потому что тип словаря не поддержка дополнение. Теперь давайте расширим класс dictionary и добавим " _ _ add__" магический метод:

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Теперь он дает следующий вывод.

{1: 'ABC', 2: 'EFG'}

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

Я надеюсь, что это делает вещи ясными для вас. Для получения дополнительной информации см.:

руководство по магическим методам Python (Рейф Кеттлер, 2012)


некоторые из этих функций делают больше, чем один метод мог бы реализовать (без абстрактных методов на суперклассе). Например,bool() действует примерно так:

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

вы можете быть на 100% уверены, что bool() всегда будет возвращать True или False; если вы полагались на метод, вы не могли быть полностью уверены, что получите обратно.

некоторые другие функции, которые имеют относительно сложные реализации (более сложные, чем базовая магия методы, вероятно, будут) являются iter() и cmp(), и все методы атрибутов (getattr, setattr и delattr). Такие вещи, как int также доступ к магическим методам при выполнении принуждения (вы можете реализовать __int__), но сделать двойную обязанность как типы. len(obj) на самом деле это один случай, когда я не верю, что он когда-либо отличается от obj.__len__().


на самом деле это не "волшебные имена". Это просто интерфейс, который объект должен реализовать для предоставления данной службы. В этом смысле они не более волшебны, чем любое предопределенное определение интерфейса, которое вы должны переопределить.


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

некоторые операции в Python реализованы как методы, например list.index и dict.append, в то время как другие реализованы как вызываемые и магические методы, например str и iter и reversed. Две группы достаточно различаются, поэтому оправдан различный подход:

  1. они общий.
  2. str, int и друзья-это типы. Имеет смысл вызвать конструктор.
  3. реализация отличается от вызова функции. Например, iter можно назвать __getitem__ если __iter__ недоступен, и поддерживает дополнительные аргументы, которые не вписываются в вызове метода. По той же причине it.next() изменено на next(it) в последних версиях Python-это имеет больше смысла.
  4. некоторые из них являются близкими родственниками операторы. Существует синтаксис для вызова __iter__ и __next__ - это называется for петли. Для согласованности лучше использовать функцию. И это делает его лучше для определенных оптимизаций.
  5. некоторые из функций просто слишком похожи на остальные в некотором роде -repr действует как str делает. Имея str(x) и x.repr() вводило бы в заблуждение.
  6. некоторые из них редко используют фактический метод реализации, например isinstance.
  7. некоторые из них являются фактическими операторами, getattr(x, 'a') это другой способ сделать x.a и getattr разделяет многие из вышеупомянутых качеств.

я лично вызываю первый групповой метод-подобный и второй групповой оператор-подобный. Это не очень хорошее различие, но я надеюсь, что это как-то помогает.

сказав это, len не совсем вписывается во вторую группу. Это более близко к операциям в первом, с той лишь разницей, что это более распространено, чем почти любой из них. Но единственное, что он делает, это зовет __len__, а это очень близко к L.index. Однако есть некоторые различия. Например, __len__ может быть вызвано для реализации других функций, таких как bool, если метод был вызван len вы могли бы сломать bool(x) с пользовательскими len метод, который делает совершенно разные вещи.

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


не так много, чтобы добавить к вышеупомянутым двум сообщениям, но все "волшебные" функции на самом деле не являются волшебными. Они являются частью модуля__ builtins__, который неявно/автоматически импортируется при запуске интерпретатора. Т. е.:

from __builtins__ import *

происходит каждый раз перед запуском программы.

Я всегда думал, что было бы правильнее, если бы Python делал это только для интерактивной оболочки и требовал скриптов для импорта различных частей из builtins, которые им нужны. Также, вероятно, другая обработка _ _ main_ _ была бы хорошей в shells vs interactive. В любом случае, проверьте все функции и посмотрите, каково это без них:

dir (__builtins__)
...
del __builtins__