我最近一直在玩 Python,我发现有点奇怪的是“魔术方法”的广泛使用,例如,为了使其长度可用,一个对象实现一个方法,def __len__(self)
然后在什么时候调用它你写len(obj)
。
我只是想知道为什么对象不简单地定义一个len(self)
方法并将它作为对象的成员直接调用,例如obj.len()
?我确信 Python 这样做肯定有充分的理由,但作为一个新手,我还没有弄清楚它们是什么。
我最近一直在玩 Python,我发现有点奇怪的是“魔术方法”的广泛使用,例如,为了使其长度可用,一个对象实现一个方法,def __len__(self)
然后在什么时候调用它你写len(obj)
。
我只是想知道为什么对象不简单地定义一个len(self)
方法并将它作为对象的成员直接调用,例如obj.len()
?我确信 Python 这样做肯定有充分的理由,但作为一个新手,我还没有弄清楚它们是什么。
AFAIK,len
在这方面很特别,有历史渊源。
这是常见问题解答中的引述:
为什么 Python 对某些功能(例如 list.index())使用方法,而对其他功能(例如 len(list))使用方法?
主要原因是历史。函数用于那些对一组类型是通用的操作,并且这些操作甚至适用于根本没有方法的对象(例如元组)。当您使用 Python 的功能特性(map()、apply() 等)时,拥有一个可以轻松应用于无定形对象集合的函数也很方便。
事实上,将 len()、max()、min() 实现为内置函数实际上比将它们实现为每种类型的方法所需的代码更少。人们可以对个别案例争论不休,但它是 Python 的一部分,现在做出如此根本的改变为时已晚。必须保留这些功能以避免大量代码损坏。
其他“魔法方法”(实际上在 Python 民间传说中称为特殊方法)很有意义,其他语言中也存在类似的功能。它们主要用于在使用特殊语法时被隐式调用的代码。
例如:
等等...
来自 Python 之禅:
面对模棱两可,拒绝猜测的诱惑。
应该有一种——最好只有一种——明显的方法来做到这一点。
这是原因之一 - 使用自定义方法,开发人员可以自由选择不同的方法名称,如,getLength()
或其他任何名称。Python 强制执行严格的命名,以便可以使用通用函数。length()
getlength()
len()
对于许多类型的对象而言,所有常见的操作都被放入魔术方法中,__nonzero__
例如__len__
或__repr__
。不过,它们大多是可选的。
运算符重载也可以通过魔术方法(例如__le__
)完成,因此也可以将它们用于其他常见操作。
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)
其中一些函数不仅仅可以实现单个方法(在超类上没有抽象方法)。例如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()
and cmp()
,以及所有属性方法(getattr
, setattr
and delattr
)。诸如int
在进行强制转换时也可以访问魔术方法(您可以实现__int__
)之类的事情,但是作为类型具有双重职责。 len(obj)
实际上是我不相信它与obj.__len__()
.
它们并不是真正的“神奇名称”。它只是对象必须实现以提供给定服务的接口。从这个意义上说,它们并不比您必须重新实现的任何预定义接口定义更神奇。
虽然原因主要是历史性的,但 Python 中有一些特性len
使得使用函数而不是适当的方法。
Python 中的一些操作被实现为方法,例如list.index
and dict.append
,而另一些操作被实现为可调用对象和魔术方法,例如str
anditer
和reversed
。这两组差异很大,因此不同的方法是合理的:
str
,int
和朋友是类型。调用构造函数更有意义。iter
可能会调用,并支持不适合方法调用的其他参数。出于同样的原因,在最新版本的 Python中已更改为- 它更有意义。__getitem__
__iter__
it.next()
next(it)
__iter__
和的语法__next__
- 它被称为for
循环。为了一致性,函数更好。它使某些优化变得更好。repr
就像做的str
那样。有str(x)
vsx.repr()
会令人困惑。isinstance
.getattr(x, 'a')
是另一种做事方式,x.a
并getattr
具有许多上述品质。我个人称第一组类似方法,第二组类似运算符。这不是一个很好的区别,但我希望它以某种方式有所帮助。
话虽如此,len
并不完全适合第二组。它更接近第一个中的操作,唯一的区别是它比几乎任何一个都更常见。但它唯一能做的就是调用__len__
,而且它非常接近L.index
。但是,存在一些差异。例如,__len__
可能会调用其他功能的实现,例如bool
,如果调用了该方法,len
您可能会与执行完全不同的事情的bool(x)
自定义方法中断。len
简而言之,您有一组非常常见的特性,类可能实现这些特性,这些特性可以通过运算符、特殊函数(通常比实现更多,就像运算符那样)、在对象构造期间以及所有这些特性进行访问分享一些共同的特点。其余的都是一种方法。并且len
在某种程度上是该规则的例外。
上面两篇文章没什么可补充的,但所有的“神奇”功能都不是真正的神奇。它们是 __builtins__ 模块的一部分,该模块在解释器启动时隐式/自动导入。IE:
from __builtins__ import *
每次在您的程序开始之前都会发生。
我一直认为如果 Python 只为交互式 shell 这样做会更正确,并且需要脚本从他们需要的内置函数中导入各个部分。也可能不同的 __ main__ 处理在 shell 与交互式中会很好。无论如何,检查所有功能,看看没有它们会是什么样子:
dir (__builtins__)
...
del __builtins__