问题:为什么Python使用“魔术方法”?
我最近一直在使用Python,而我发现有点奇怪的是,广泛使用了“魔术方法”,例如,使其长度可用,一个对象实现一个方法,def __len__(self)
然后在你写len(obj)
。
我只是想知道为什么对象不能简单地定义一个len(self)
方法并直接将其作为对象的成员来调用,例如obj.len()
?我敢肯定,Python这么做的确有充分的理由,但是作为一个新手,我还没有弄清楚它们到底是什么。
回答 0
AFAIK len
在这方面很特别,并且具有历史渊源。
这是FAQ中的报价:
为什么Python使用方法来实现某些功能(例如list.index()),却使用其他方法(例如len(list))呢?
主要原因是历史。函数用于那些对一组类型通用的操作,即使对于根本没有方法的对象(例如,元组),这些功能也可以使用。使用Python的功能部件(map(),apply()等)时,具有可以轻松应用于对象的不定形集合的函数也很方便。
实际上,将len(),max(),min()实现为内置函数实际上比将它们实现为每种类型的方法要少。人们可能会质疑个别情况,但这是Python的一部分,现在进行这样的基本更改为时已晚。必须保留功能以避免大量代码损坏。
其他“魔术方法”(在Python民俗中实际上称为特殊方法)很有道理,其他语言中也存在类似的功能。它们通常用于使用特殊语法时隐式调用的代码。
例如:
- 重载运算符(存在于C ++和其他语言中)
- 构造函数/析构函数
- 用于访问属性的挂钩
- 元编程工具
等等…
回答 1
从Python的Zen中:
面对模棱两可的想法,拒绝猜测的诱惑。
应该有一种-最好只有一种-显而易见的方法。
这是原因之一-自定义方法,开发人员可以自由选择不同的方法的名称,如getLength()
,length()
,getlength()
或任何责任。Python强制执行严格的命名,以便len()
可以使用通用功能。
这是常见的许多类型的对象,所有的操作都投入到神奇的方法,比如__nonzero__
,__len__
或者__repr__
。不过,它们大多是可选的。
运算符重载也可以通过魔术方法(例如__le__
)完成,因此也可以将它们用于其他常见操作。
回答 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)
回答 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()
,和所有的属性的方法(getattr
,setattr
和delattr
)。诸如此类的事物int
在执行强制时也可以访问魔术方法(您可以实现__int__
),但是作为类型承担双重责任。 len(obj)
实际上是一种我认为与没什么不同的情况obj.__len__()
。
回答 4
它们并不是真正的“魔术名称”。它只是对象必须实现以提供给定服务的接口。从这个意义上讲,它们没有比您必须重新实现的任何预定义接口定义更神奇。
回答 5
尽管原因主要是历史性的,但Python的特性有其特殊之处 len
使得使用函数而不是适当的方法成为可能。
Python中的某些操作被实现为方法,例如list.index
和dict.append
,而另一些则是可调用和神奇的方法来实现,例如str
和iter
和reversed
。两组之间的差异足够大,因此可以采用不同的方法:
- 它们很常见。
str
,int
而朋友是类型。调用构造函数更有意义。- 实现与函数调用不同。例如,
iter
可能__getitem__
在if__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)
与customlen
做完全不同的事情的方法。
简而言之,您可以拥有一组类可能实现的非常通用的功能,这些功能可以通过操作员,在对象构造期间通过特殊功能(通常比实现操作员做得更多)执行的特殊功能访问,并且在所有这些操作中具有一些共同的特征。其余所有方法。这len
是该规则的一个exceptions。
回答 6
上面的两个帖子没有太多补充,但是所有的“魔术”功能根本不是魔术。它们是__builtins__模块的一部分,该模块在解释器启动时隐式/自动导入。即:
from __builtins__ import *
程序每次启动之前都会发生。
我一直认为,如果Python仅对交互式外壳执行此操作,并且需要脚本从所需的内置文件中导入各个部分,那会更正确。同样,不同的__ main__处理在shell还是交互式中会更好。无论如何,请检查所有功能,看看没有它们的情况是什么:
dir (__builtins__)
...
del __builtins__