Почему Python использует «магические методы»? - PullRequest
96 голосов
/ 17 апреля 2010

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

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

Ответы [ 7 ]

62 голосов
/ 17 апреля 2010

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

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

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

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

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

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

Например:

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

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

20 голосов
/ 17 апреля 2010

Из дзен питона:

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

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

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

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

15 голосов
/ 06 сентября 2016

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 (Rafe Kettler, 2012)

9 голосов
/ 17 апреля 2010

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

4 голосов
/ 17 апреля 2010

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

1 голос
/ 05 марта 2011

Хотя причина в основном историческая, в 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 является чем-то вроде исключения из этого правила.

0 голосов
/ 20 апреля 2010

К вышеперечисленным двум постам добавить нечего, но все «магические» функции вовсе не являются волшебными. Они являются частью модуля __buildins__, который неявно / автоматически импортируется при запуске интерпретатора. I.e.:

from __builtins__ import *

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

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

dir (__builtins__)
...
del __builtins__
...