我试图从概念上理解Python函数和方法的本质。我得到的函数实际上是对象,使用在执行函数时调用的方法。但是,函数对象方法实际上是另一个函数吗?
例如:
def fred():
pass
如果我查看dir(fred)
,我会看到它有一个名为__call__
的属性。但dir(fred.__call__)
还有一个名为__call__
的属性。 fred.__call__.__call__
等也是如此。这个__call__
对象链的ID表明它们都是截然不同的。它们真的是对象还是解释器的一些低级技巧?
哪个更基础:函数或对象方法?
答案 0 :(得分:4)
简短回答:两者都是基本的,.__call__()
关于函数只是一个虚拟技巧。
这个答案的其余部分有点复杂。你不必理解它,但我觉得这个主题很有趣。请注意,我将提出一系列谎言,逐步修复它们。
在最基础的层面上,Python可以说只有2个操作:
obj.attr
callable(args)
方法调用 - obj.method(args)
- 不是基本的。它们包含两个步骤:获取属性obj.method
(提供可调用的“绑定方法”对象)并使用args
调用该属性。
其他运营商就其定义。例如。 x + y
尝试x.__add__(y)
,如果不起作用,则回退到其他类似的组合。
到目前为止一切顺利。但是调用和属性访问本身也是根据obj.__call__(args)
和obj.__getattribute__(name)
定义的?!?
它是乌龟一路下来的吗?!?
诀窍是对象上的操作是通过调用类型的方法来定义的:type(obj).__call__(obj, args)
和type(obj).__getattribute__(obj, name)
。哪个BTW意味着我欺骗了你,并且还有第三个基本操作:
type(obj)
好的,这仍然没有帮助。 type(obj).__call__(...)
仍然涉及属性访问和通话,因此这应该无限期地继续?问题是最终你遇到了一个内置类型 - 通常是一个函数,object
或type
- 对于它们,属性访问和函数调用是基本
因此,当您调用自定义类的实例时,确实是通过其__call__
方法实现的。但它的__call__
方法可能是一个正常的函数 - 可以直接调用。神秘的结束。
类似于__getattribute__
- 你可以为它提供 define 属性访问权限,但是类本身实现了属性访问(除非它有一个自定义元类)。
那为什么即使一个函数有一个fred.__call__
方法呢?嗯,这只是吸引力来反映内置类型和自定义类之间的差异。这个方法存在于所有可调用对象上,但调用普通函数不必经过它 - 函数基本上是可调用的。
同样,所有对象都有obj.__getattribute__
和obj.__class__
,但对于内置类型,只需公开基本操作,而不是定义它
第一个声称Python有两个基本操作的说法实际上是一个完全的谎言。从技术上讲,所有Python运算符在C级别都有一个“基本”操作,通过方法公开一致性,而自定义类可以通过类似的方法重新定义这些操作。
但我告诉你的故事可能是真的,它减少了问题的中心:为什么.__call__()
和.__getattribute__()
不是无限递归。
答案 1 :(得分:2)
不是特定的Python答案,但在最低级别,处理器只能理解操作和变量。从那里我们推断出函数,从变量和函数我们推断出对象。因此,从低级编程的角度来看,我会说更重要的是函数。
对于Pythonic意义上的Python来说,这不一定是正确的,并且可能是一个很好的例子,说明为什么用它来深入研究语言的实现并不总是有益的。 :)将函数视为对象当然是Python本身更好的答案。
起初我认为你的调用是跟踪Python库的,但.call方法与任何其他方法具有相同的属性。因此,我认为,它使用python CLI进行了几分钟的递归探索。我认为这是探索体系结构的一种痛苦方式,而不一定是Python处理对象的属性的错误。 :)
答案 2 :(得分:1)
哪个更基本:功能 或对象方法?
我认为最好的答案可能是“不”。请参阅Python参考的Execution model部分,其中引用“块”。这是实际执行的内容。你在无限搜索结束时遇到的__call__
事件只是一个知道如何执行代码块的包装器(参见函数的各种func_xxx
属性,而不是实际的字节码存储为func_code
)。
同样相关的是Function definitions部分,它引用了“一个函数对象[是](函数的可执行代码的包装器)”。最后,有callable这个词,也可能是“哪个更基本?”的答案。