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


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

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

7 92

7 ответов:

насколько я знаю, 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'

Это дает ошибку, потому что тип словаря не поддержка дополнения. Теперь давайте расширим класс словаря и добавим " _ _ 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, в то время как другие реализуются как callables и магические методы, например 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 делал это только для интерактивной оболочки и требовал скриптов для импорта различных частей из встроенных файлов, которые им нужны. Также, вероятно, различная__ main _ _ обработка была бы хороша в shells vs interactive. В любом случае, проверьте все функции, и посмотреть, что это такое, как без них:

dir (__builtins__)
...
del __builtins__