繁体   English   中英

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

[英]Why does Python use 'magic methods'?

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

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

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

这是FAQ的引用:

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

主要原因是历史。 函数用于那些对一组类型通用的操作,这些操作甚至可以用于根本没有方法的对象(例如元组)。 当您使用Python的功能特性(map(),apply()等)时,拥有一个可以很容易地应用于无定形对象集合的函数也很方便。

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

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

例如:

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

等等...

来自Python的禅宗:

面对模棱两可,拒绝猜测的诱惑。
应该有一个 - 最好只有一个 - 明显的方法来做到这一点。

这是原因之一 - 使用自定义方法,开发人员可以自由选择不同的方法名称,如getLength()length()getlength()或任何方法。 Python强制执行严格命名,以便可以使用公共函数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()cmp() ,以及所有属性方法( getattrsetattrdelattr )。 int这样的东西在执行强制时也可以访问魔法(你可以实现__int__ ),但是作为类型执行双重任务。 len(obj)实际上是我认为它与obj.__len__()不同的一种情况。

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

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

Python中的一些操作实现为方法,例如list.indexdict.append ,而另一些则是可调用和魔术方法实现,例如striterreversed 两组的差异很大,因此不同的方法是合理的:

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

我个人称第一组方法和第二组操作员一样。 这不是一个很好的区别,但我希望它有所帮助。

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

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

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

from __builtins__ import *

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

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

dir (__builtins__)
...
del __builtins__

也许,您已经注意到可以在某些本机类型(例如list )上使用某些内置方法(例如len(my_list_or_my_string) )和语法(例如my_list_or_my_string[:3]my_fancy_dict['some_key'] ), dict 也许您一直好奇为什么(还)不能在您编写的某些类上使用这些相同的语法。

本机类型的变量( listdictintstr )具有独特的行为并响应某些语法,因为它们在各自的类中定义了一些特殊的方法——这些方法称为魔术方法。

一些魔术方法包括: __len____gt____eq__等。

在这里阅读更多: https ://tomisin.dev/blog/supercharging-python-classes-with-magic-methods

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM