为什么Python使用'魔术方法'?

时间:2010-04-17 07:26:25

标签: python magic-methods

我最近一直在玩Python,有一件事我觉得有点奇怪的是广泛使用'魔术方法',例如为了使其长度可用,对象实现了一个方法def __len__(self),然后在您编写len(obj)时调用它。

我只是想知道为什么对象不是简单地定义len(self)方法并且直接将其作为对象的成员调用,例如obj.len()?我确信Python必须有充分的理由这样做,但作为一个新手,我还没有弄清楚它们是什么。

7 个答案:

答案 0 :(得分:62)

AFAIK,len在这方面很特别,并且具有历史根源。

这是引用from the FAQ

  

为什么Python会使用某些方法   功能(例如list.index())但是   其他的功能(例如len(list))?

     

主要原因是历史。功能   用于那些操作   对于一组类型而言是通用的   它们的目的是为了工作   没有方法的对象   所有(例如元组)。也是   方便有一个功能可以   容易应用于无定形   使用时的对象集合   Python的功能特性(map(),   apply()et al)。

     

实际上,实现len(),max(),   min()作为内置函数   实际上代码少于实现   它们作为每种类型的方法。一罐   关于个别案件的狡辩但是   它是Python的一部分,它也是   迟到做出这样根本性的改变   现在。功能必须保持   避免大规模的代码破坏。

其他“神奇的方法”(在Python民间传说中实际上称为特殊方法)很有意义,其他语言也存在类似的功能。它们主要用于在使用特殊语法时隐式调用的代码。

例如:

  • 重载运算符(存在于C ++和其他中)
  • 构造/析构
  • 用于访问属性的钩子
  • 元编程工具

依旧......

答案 1 :(得分:20)

来自Python的禅宗:

  

面对模糊,拒绝猜测的诱惑      应该有一个 - 最好只有一个 - 显而易见的方法。

这是原因之一 - 使用自定义方法,开发人员可以自由选择其他方法名称,例如getLength()length()getlength()或任何方法。 Python强制执行严格命名,以便可以使用公共函数len()

许多类型对象常见的所有操作都被放入魔术方法中,例如__nonzero____len____repr__。但它们大多是可选的。

运算符重载也是通过魔术方法完成的(例如__le__),因此将它们用于其他常见操作也是有意义的。

答案 2 :(得分:15)

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

因此,通过添加这种方法,突然发生魔法并且你早些时候出现的错误已经消失。

我希望,它能让你清楚。有关更多信息,请参阅:

A Guide to Python's Magic Methods (Rafe Kettler,2012)

答案 3 :(得分:9)

其中一些函数可以实现多个方法(没有超类上的抽象方法)。例如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,{ {1}}和setattr)。像delattr这样的东西在进行强制攻击时也会访问魔法(你可以实现int),但是作为类型执行双重任务。 __int__实际上是我不相信它与len(obj)不同的一个案例。

答案 4 :(得分:4)

他们不是真正的“魔术名字”。它只是一个对象必须实现的接口才能提供给定的服务。从这个意义上说,它们并不比你必须重新实现的任何预定义接口定义更具魔力。

答案 5 :(得分:1)

虽然原因主要是历史性的,但Python的len中存在一些特性,它们使用函数而不是适当的方法。

Python中的一些操作是作为方法实现的,例如list.indexdict.append,而其他操作则实现为callables和magic方法,例如striterreversed。两组的差异很大,因此不同的方法是合理的:

  1. 他们很常见。
  2. strint和朋友是类型。调用构造函数更有意义。
  3. 实现与函数调用不同。例如,如果iter不可用,__getitem__可能会调用__iter__,并支持不适合方法调用的其他参数。出于同样的原因,it.next()在最新版本的Python中已更改为next(it) - 这更有意义。
  4. 其中一些是运营商的近亲。调用__iter____next__的语法 - 它被称为for循环。为了保持一致性,功能更好。并且它使某些优化更好。
  5. 某些功能在某种程度上与其他功能过于相似 - repr就像str一样。 str(x)x.repr()之间存在混淆。
  6. 其中一些很少使用实际的实现方法,例如isinstance
  7. 其中一些是实际的操作符,getattr(x, 'a')是另一种做法x.agetattr分享许多上述特质的方法。
  8. 我个人称第一组方法和第二组操作员一样。这不是一个很好的区别,但我希望它有所帮助。

    话虽如此,len并不完全适合第二组。它更接近于第一个中的操作,唯一的区别是它比几乎任何一个都更常见。但它唯一能做的就是调用__len__,它非常接近L.index。但是,存在一些差异。例如,可能会调用__len__来实现其他功能,例如bool,如果方法被调用len,您可能会使用自定义{{1}中断bool(x)完全不同的方法。

    简而言之,在对象构造期间,您可以通过一个特殊的函数(通常比实现更多,作为运算符)来实现可以通过运算符访问的类可能实现的一组非常常见的功能,以及他们都有一些共同的特点。其余的都是一种方法。而且len在某种程度上是一个例外。

答案 6 :(得分:0)

上面两篇文章中没有太多内容可以添加,但所有“魔术”功能都不是真正的魔术。它们是__ builtins__模块的一部分,它在解释器启动时隐式/自动导入。即:

from __builtins__ import *

每次程序开始前都会发生。

我一直认为如果Python只针对交互式shell执行此操作会更正确,并且需要脚本从他们需要的内置导入各个部分。也许不同的__ main__处理在shell和交互式中会很好。无论如何,检查所有功能,看看没有它们是什么样的:

dir (__builtins__)
...
del __builtins__