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

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

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

Ответ 1

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

Здесь цитата из ЧаВо:

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

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

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

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

Например:

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

и т.д.

Ответ 2

Из Zen Python:

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

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

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

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

Ответ 3

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'}

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

Надеюсь, это прояснит вам. Для получения дополнительной информации см. Ссылку ниже: -

http://web.archive.org/web/20161024123835/http://www.rafekettler.com/magicmethods.html

Ответ 4

Некоторые из этих функций реализуют более одного метода (без абстрактных методов над суперклассом). Например, 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__().

Ответ 5

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

from __builtins__ import *

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

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

dir (__builtins__)
...
del __builtins__