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

问题:为什么Python使用“魔术方法”?

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

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

I’ve been playing around with Python recently, and one thing I’m finding a bit odd is the extensive use of ‘magic methods’, e.g. to make its length available, an object implements a method, def __len__(self), and then it is called when you write len(obj).

I was just wondering why objects don’t simply define a len(self) method and have it called directly as a member of the object, e.g. obj.len()? I’m sure there must be good reasons for Python doing it the way it does, but as a newbie I haven’t worked out what they are yet.


回答 0

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

这是FAQ中的报价:

为什么Python使用方法来实现某些功能(例如list.index()),却使用其他方法(例如len(list))呢?

主要原因是历史。函数用于那些对一组类型通用的操作,即使对于根本没有方法的对象(例如,元组),这些功能也可以使用。使用Python的功能部件(map(),apply()等)时,具有可以轻松应用于对象的不定形集合的函数也很方便。

实际上,将len(),max(),min()实现为内置函数实际上比将它们实现为每种类型的方法要少。人们可能会质疑个别情况,但这是Python的一部分,现在进行这样的基本更改为时已晚。必须保留功能以避免大量代码损坏。

其他“魔术方法”(在Python民俗中实际上称为特殊方法)很有道理,其他语言中也存在类似的功能。它们通常用于使用特殊语法时隐式调用的代码。

例如:

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

等等…

AFAIK, len is special in this respect and has historical roots.

Here’s a quote from the FAQ:

Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

The major reason is history. Functions were used for those operations that were generic for a group of types and which were intended to work even for objects that didn’t have methods at all (e.g. tuples). It is also convenient to have a function that can readily be applied to an amorphous collection of objects when you use the functional features of Python (map(), apply() et al).

In fact, implementing len(), max(), min() as a built-in function is actually less code than implementing them as methods for each type. One can quibble about individual cases but it’s a part of Python, and it’s too late to make such fundamental changes now. The functions have to remain to avoid massive code breakage.

The other “magical methods” (actually called special method in the Python folklore) make lots of sense, and similar functionality exists in other languages. They’re mostly used for code that gets called implicitly when special syntax is used.

For example:

  • overloaded operators (exist in C++ and others)
  • constructor/destructor
  • hooks for accessing attributes
  • tools for metaprogramming

and so on…


回答 1

从Python的Zen中:

面对模棱两可的想法,拒绝猜测的诱惑。
应该有一种-最好只有一种-显而易见的方法。

这是原因之一-自定义方法,开发人员可以自由选择不同的方法的名称,如getLength()length()getlength()或任何责任。Python强制执行严格的命名,以便len()可以使用通用功能。

这是常见的许多类型的对象,所有的操作都投入到神奇的方法,比如__nonzero____len__或者__repr__。不过,它们大多是可选的。

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

From the Zen of Python:

In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.

This is one of the reasons – with custom methods, developers would be free to choose a different method name, like getLength(), length(), getlength() or whatsoever. Python enforces strict naming so that the common function len() can be used.

All operations that are common for many types of objects are put into magic methods, like __nonzero__, __len__ or __repr__. They are mostly optional, though.

Operator overloading is also done with magic methods (e.g. __le__), so it makes sense to use them for other common operations, too.


回答 2

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)

Python uses the word “magic methods”, because those methods really performs magic for you program. One of the biggest advantages of using Python’s magic methods is that they provide a simple way to make objects behave like built-in types. That means you can avoid ugly, counter-intuitive, and nonstandard ways of performing basic operators.

Consider a following example:

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'

This gives an error, because the dictionary type doesn’t support addition. Now, let’s extend dictionary class and add “__add__” magic method:

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Now, it gives following output.

{1: 'ABC', 2: 'EFG'}

Thus, by adding this method, suddenly magic has happened and the error you were getting earlier, has gone away.

I hope, it makes things clear to you. For more information, refer to:

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


回答 3

这些功能中的某些功能不仅仅可以实现单个方法(在超类上没有抽象方法)也可以实现。例如,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__),但是作为类型承担双重责任。 len(obj)实际上是一种我认为与没什么不同的情况obj.__len__()

Some of these functions do more than a single method would be able to implement (without abstract methods on a superclass). For instance bool() acts kind of like this:

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

You can also be 100% sure that bool() will always return True or False; if you relied on a method you couldn’t be entirely sure what you’d get back.

Some other functions that have relatively complicated implementations (more complicated than the underlying magic methods are likely to be) are iter() and cmp(), and all the attribute methods (getattr, setattr and delattr). Things like int also access magic methods when doing coercion (you can implement __int__), but do double duty as types. len(obj) is actually the one case where I don’t believe it’s ever different from obj.__len__().


回答 4

它们并不是真正的“魔术名称”。它只是对象必须实现以提供给定服务的接口。从这个意义上讲,它们没有比您必须重新实现的任何预定义接口定义更神奇。

They are not really “magic names”. It’s just the interface an object has to implement to provide a given service. In this sense, they are not more magic than any predefined interface definition you have to reimplement.


回答 5

尽管原因主要是历史性的,但Python的特性有其特殊之处 len使得使用函数而不是适当的方法成为可能。

Python中的某些操作被实现为方法,例如list.indexdict.append,而另一些则是可调用和神奇的方法来实现,例如striterreversed。两组之间的差异足够大,因此可以采用不同的方法:

  1. 它们很常见。
  2. strint而朋友是类型。调用构造函数更有意义。
  3. 实现与函数调用不同。例如,iter可能__getitem__在if __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)与customlen做完全不同的事情的方法。

简而言之,您可以拥有一组类可能实现的非常通用的功能,这些功能可以通过操作员,在对象构造期间通过特殊功能(通常比实现操作员做得更多)执行的特殊功能访问,并且在所有这些操作中具有一些共同的特征。其余所有方法。这len是该规则的一个exceptions。

While the reason is mostly historic, there are some peculiarities in Python’s len that make the use of a function instead of a method appropriate.

Some operations in Python are implemented as methods, for example list.index and dict.append, while others are implemented as callables and magic methods, for example str and iter and reversed. The two groups differ enough so the different approach is justified:

  1. They are common.
  2. str, int and friends are types. It makes more sense to call the constructor.
  3. The implementation differs from the function call. For example, iter might call __getitem__ if __iter__ isn’t available, and supports additional arguments that don’t fit in a method call. For the same reason it.next() has been changed to next(it) in recent versions of Python – it makes more sense.
  4. Some of these are close relatives of operators. There’s syntax for calling __iter__ and __next__ – it’s called the for loop. For consistency, a function is better. And it makes it better for certain optimisations.
  5. Some of the functions are simply way too similar to the rest in some way – repr acts like str does. Having str(x) versus x.repr() would be confusing.
  6. Some of them rarely use the actual implementation method, for example isinstance.
  7. Some of them are actual operators, getattr(x, 'a') is another way of doing x.a and getattr shares many of the aforementioned qualities.

I personally call the first group method-like and the second group operator-like. It’s not a very good distinction, but I hope it helps somehow.

Having said this, len doesn’t exactly fit in the second group. It’s more close to the operations in the first one, with the only difference that it’s way more common than almost any of them. But the only thing that it does is calling __len__, and it’s very close to L.index. However, there are some differences. For example, __len__ might be called for the implementation of other features, such as bool, if the method was called len you might break bool(x) with custom len method that does completely different thing.

In short, you have a set of very common features that classes might implement that might be accessed through an operator, through a special function (that usually does more than the implementation, as an operator would), during object construction, and all of them share some common traits. All the rest is a method. And len is somewhat of an exception to that rule.


回答 6

上面的两个帖子没有太多补充,但是所有的“魔术”功能根本不是魔术。它们是__builtins__模块的一部分,该模块在解释器启动时隐式/自动导入。即:

from __builtins__ import *

程序每次启动之前都会发生。

我一直认为,如果Python仅对交互式外壳执行此操作,并且需要脚本从所需的内置文件中导入各个部分,那会更正确。同样,不同的__ main__处理在shell还是交互式中会更好。无论如何,请检查所有功能,看看没有它们的情况是什么:

dir (__builtins__)
...
del __builtins__

There is not a lot to add to the above two posts, but all the “magic” functions are not really magic at all. They are part of the __ builtins__ module which is implicitly/automatically imported when the interpreter starts. I.e.:

from __builtins__ import *

happens every time before your program starts.

I always thought it would be more correct if Python only did this for the interactive shell, and required scripts to import the various parts from builtins they needed. Also probably different __ main__ handling would be nice in shells vs interactive. Anyway, check out all the functions, and see what it is like without them:

dir (__builtins__)
...
del __builtins__