为什么python使用“魔术方法”?

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

我只是想知道为什么对象不会简单地定义一个len(self)方法,并直接调用它作为对象的成员,例如obj.len() ? 我相信Python一定有很好的理由这样做,但作为一个新手,我还没有弄清楚他们到底是什么。

AFAIK, len在这方面是特殊的,有历史根源。

以下是常见问题解答中的一段引文:

为什么Python使用一些function的方法(如list.index()),但其他function(例如len(列表))?

主要原因是历史。 函数被用于那些对于一组types是通用的操作,并且即使对于根本没有方法的对象(例如,元组)也可以工作。 当你使用Python的function特性(map(),apply()等)时,有一个函数可以很容易地应用到非对象集合中。

实际上,实现len(),max(),min()作为一个内置的函数,实际上比实现它们的方法更less。 人们可以对个别案例进行质疑,但是它是Python的一部分,现在就做出这种根本性的改变为时已晚。 函数必须保持以避免大量的代码破坏。

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

例如:

  • 重载的操作符(存在于C ++等)
  • 构造函数/析构函数
  • 挂钩访问属性
  • 元编程工具

等等…

从Python的禅:

面对歧义,拒绝猜测的诱惑。
应该有一个 – 最好只有一个 – 明显的方法来做到这一点。

这是原因之一 – 使用自定义方法,开发人员可以自由select不同的方法名称,如getLength()length()getlength()或任何其他方法。 Python强制严格命名,以便可以使用常用函数len()

对于许多types的对象来说,所有的操作都被放入魔法方法中,比如__nonzero____repr____repr__ 。 不过,他们大多是可选的。

运算符重载也是用魔术方法完成的(比如__le__ ),所以在其他常用的操作中也是有意义的。

Python使用这个词: – “魔术方法”,因为这些方法真的为你编程。 使用Python神奇方法的最大优点之一是它们提供了一个简单的方法来使对象像内置types一样运行。 这意味着你可以避免丑陋,违反直觉和非标准的方式来执行基本的操作。

考虑下面的例子:

 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' 

这给出了一个错误,因为字典types不支持添加。 现在,我们扩展字典类并添加“__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

这些函数中的一些函数可以实现多个单一方法(在超类上没有抽象方法)。 例如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()以及所有属性方法( getattrsetattrdelattr )都有一些相对复杂的函数(比底层魔法方法更复杂)。 像int这样的东西在进行强制操作时也可以使用魔术方法(你可以实现__int__ ),但是可以双重使用types。 len(obj)实际上是我不相信与obj.__len__()不同的一种情况。

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

虽然原因大部分是历史性的,但是在Python的len中有一些特殊性,它们使用了一个函数而不是一个合适的方法。

Python中的一些操作被实现为方法,例如list.indexdict.append ,而另外一些则作为可调用和魔术方法来实现,例如striter以及reversed 。 这两个组别有所不同,所以不同的方法是合理的:

  1. 它们很常见。
  2. strint和friends是types。 调用构造函数更有意义。
  3. 实现不同于函数调用。 例如,如果__iter__不可用, iter可能会调用__getitem__ ,并支持不适合方法调用的其他参数。 出于同样的原因, it.next()在Python的最新版本中已经改为next(it) – 这样做更有意义。
  4. 其中一些是运营商的近亲。 有调用__iter____next__的语法 – 它被称为for循环。 为了一致性,function更好。 而且这对于某些优化是更好的。
  5. 有些function在某种程度上与其他function太相似了 – repr就像str一样。 有str(x)x.repr()会混淆。
  6. 其中一些很less使用实际的实现方法,例如isinstance
  7. 其中一些是实际的操作符, getattr(x, 'a')是另一种做xagetattr具有许多上述的特性。

我亲自打电话给第一组方法和第二组运算符一样。 这不是一个很好的区别,但我希望它有助于某种方式。

话虽如此, len并不完全适合第二组。 它比较接近于第一个的操作,唯一的区别是它比几乎任何一个都更普遍。 但是它唯一做的就是调用__len__ ,而且它非常接近L.index 。 但是,有一些差异。 例如,可能会调用__len__来实现其他function,例如bool ,如果该方法被称为len ,则可能会使用自定义的len方法打破bool(x) ,该方法完全不同。

简而言之,您有一组非常常见的特性,类可能实现的特性可以通过操作符,通过特殊的函数(通常不仅仅是实现,就像操作员那样)在对象构造过程中进行访问,而且所有这些特性有一些共同的特点。 其余的都是一种方法。 而len是这个规则的一个例外。

上面的两个post没有太多的补充,但是所有的“魔术”function都不是真正的魔术。 它们是__ builtins__模块的一部分,当解释器启动时,它是隐含/自动导入的。 IE:

 from __builtins__ import * 

每次在您的程序启动之前发生。

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

 dir (__builtins__) ... del __builtins__