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

发布于 2024-08-29 05:17:40 字数 292 浏览 2 评论 0原文

我对 Python 广泛使用“魔术方法”感到有点惊讶。

例如,为了让类声明实例具有“长度”,它实现了一个 __len__ 方法,当您编写 len(obj) 时会调用该方法。为什么不直接定义一个作为对象成员直接调​​用的len方法,例如obj.len()


另请参阅:为什么 Python 代码使用 len() 函数而不是 length 方法?

I'm a bit surprised by Python's extensive use of 'magic methods'.

For example, in order for a class to declare that instances have a "length", it implements a __len__ method, which it is called when you write len(obj). Why not just define a len method which is called directly as a member of the object, e.g. obj.len()?


See also: Why does Python code use len() function instead of a length method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

酒与心事 2024-09-05 05:17:40

AFAIK,len 在这方面很特殊并且有历史根源。

以下是来自常见问题解答的引用:

为什么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...

情绪 2024-09-05 05:17:40

来自 Python 之禅:

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

这是原因之一 - 使用自定义方法,开发人员可以自由选择不同的方法名称,例如 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.

那小子欠揍 2024-09-05 05:17:40

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)

滥情哥ㄟ 2024-09-05 05:17:40

其中一些函数的功能超出了单个方法所能实现的功能(在超类上没有抽象方法)。例如,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__().

跨年 2024-09-05 05:17:40

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

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.

我不吻晚风 2024-09-05 05:17:40

虽然原因主要是历史性的,但 Python 的 len 中有一些特殊性,使得使用函数而不是方法更合适。

Python 中的一些操作被实现为方法,例如 list.indexdict.append,而其他操作则被实现为可调用函数和魔术方法,例如 str< /code> 和 iter 以及 反转。这两个群体差异很大,因此采用不同的方法是合理的:

  1. 它们很常见。
  2. strint 和朋友都是类型。调用构造函数更有意义。
  3. 实现与函数调用不同。例如,如果 __iter__ 不可用,则 iter 可能会调用 __getitem__,并且支持不适合方法调用的其他参数。出于同样的原因,在最新版本的 Python 中,it.next() 已更改为 next(it) - 它更有意义。
  4. 其中一些是经营者的近亲。有调用 __iter____next__ 的语法 - 它称为 for 循环。为了一致性,函数更好。它可以更好地进行某些优化。
  5. 某些函数在某些方面与其他函数过于相似 - repr 的作用类似于 str 的作用。使用 str(x)x.repr() 会令人困惑。
  6. 其中一些很少使用实际的实现方法,例如isinstance
  7. 其中一些是实际运算符,getattr(x, 'a') 是执行 xa 的另一种方式,并且 getattr 具有许多上述品质。

我个人将第一组称为“类方法”,将第二组称为“类运算符”。这不是一个很好的区别,但我希望它能有所帮助。

话虽如此,len 并不完全适合第二组。它更接近第一个操作,唯一的区别是它比几乎所有操作都更常见。但它唯一做的就是调用__len__,并且它非常接近L.index。然而,也存在一些差异。例如,可能会调用 __len__ 来实现其他功能,例如 bool,如果调用该方法 len,您可能会破坏 >bool(x) 使用自定义的 len 方法来完成完全不同的事情。

简而言之,您有一组非常常见的功能,类可以实现这些功能,这些功能可以通过运算符、特殊函数(通常比实现执行更多操作,就像运算符那样)、在对象构造期间以及所有这些功能进行访问具有一些共同特征。剩下的都是方法。而 len 在某种程度上是该规则的一个例外。

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.

偏闹i 2024-09-05 05:17:40

上面两篇文章没有太多要补充的内容,但所有“神奇”功能其实根本就不神奇。它们是 __builtins__ 模块的一部分,在解释器启动时隐式/自动导入。即:

from __builtins__ import *

每次在程序开始之前都会发生。

我一直认为,如果 Python 只为交互式 shell 执行此操作,并且需要脚本从所需的内置函数中导入各个部分,那会更正确。另外,在 shell 和交互式中,不同的 __ main__ 处理可能会更好。不管怎样,检查一下所有的功能,看看没有它们会是什么样子:

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__
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文