Python "function objects" 是函数吗?
Are Python "function objects" functions?
我们都知道函数也是对象。但是函数对象与函数相比如何呢?函数对象和函数有什么区别?
通过函数对象,我的意思是 g 如此定义:
class G(object):
def __call__(self, a):
pass
g = G()
按功能我的意思是:
def f(a):
pass
Python 在您使用 def 语句或使用 lambda 表达式时为您创建函数对象:
>>> def foo(): pass
...
>>> foo
<function foo at 0x106aafd70>
>>> lambda: None
<function <lambda> at 0x106d90668>
因此,每当您在 python 中定义一个函数时,都会创建一个这样的对象。 没有简单的函数定义方式。
TL;DR 可以调用任何实现 __call__
的对象,例如:函数、自定义 classes 等。
稍微长一点的版本: (walloft分机)
您关于有何不同的问题的完整答案在于 python 虚拟机的实现,因此我们必须深入了解 python。首先是代码对象的概念。 Python 将您输入的任何内容解析为它自己的内部语言,这种语言在所有平台上都相同,称为字节码。一个非常直观的表示是在导入您编写的自定义库后获得 .pyc 文件。这些是 python 虚拟机的原始指令。忽略这些指令是如何从您的源代码创建的,它们随后由 Python/ceval.c 中的 PyEval_EvalFrameEx
执行。源代码有点像野兽,但最终就像一个简单的处理器一样工作,其中抽象了一些复杂的位。字节码是该处理器的汇编语言。特别是这个 "processor" 的 "opcodes" 之一是(恰当地命名)CALL_FUNCTION
。回调经过多次调用,最终到达 PyObject_Call()
。此函数采用指向 PyObject
的指针并从其类型中提取 tp_call
属性并直接调用它(从技术上讲,它首先检查它是否存在):
...
call = func->ob_type->tp_call //func is an arg of PyObject_Call() and is a pointer to a PyObject
...
result = (*call)(func, arg, kw);
任何实现 __call__
的对象都被赋予一个 tp_call
属性,并带有指向实际函数的指针。我相信这是由 Objects/typeobject.c:
的 slotdefs[]
定义处理的
FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
"__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
PyWrapperFlag_KEYWORDS)
函数的 __call__
方法本身在 cpython 实现中定义,它定义了 python VM 应该如何开始执行该函数的字节码以及数据应该如何返回(如果有)。当你给任意 class 一个 __call__
方法时,该属性是一个函数对象,它再次引用回 __call__
的 cpython 实现。因此,当您调用 "normal" 函数时,会引用 foo.__call__
。当您调用可调用的 class 时,self.__call__
等同于 foo
并且调用的实际 cpython 引用是 self.__call__.im_func.__call__
.
免责声明
对我来说,这是一段进入未知领域的旅程,我完全有可能歪曲了实施的一些细节。我主要取自this blog post on how python callables work under the hood, and some digging of my own through the python source code
我们都知道函数也是对象。但是函数对象与函数相比如何呢?函数对象和函数有什么区别?
通过函数对象,我的意思是 g 如此定义:
class G(object):
def __call__(self, a):
pass
g = G()
按功能我的意思是:
def f(a):
pass
Python 在您使用 def 语句或使用 lambda 表达式时为您创建函数对象:
>>> def foo(): pass
...
>>> foo
<function foo at 0x106aafd70>
>>> lambda: None
<function <lambda> at 0x106d90668>
因此,每当您在 python 中定义一个函数时,都会创建一个这样的对象。 没有简单的函数定义方式。
TL;DR 可以调用任何实现 __call__
的对象,例如:函数、自定义 classes 等。
稍微长一点的版本: (walloft分机)
您关于有何不同的问题的完整答案在于 python 虚拟机的实现,因此我们必须深入了解 python。首先是代码对象的概念。 Python 将您输入的任何内容解析为它自己的内部语言,这种语言在所有平台上都相同,称为字节码。一个非常直观的表示是在导入您编写的自定义库后获得 .pyc 文件。这些是 python 虚拟机的原始指令。忽略这些指令是如何从您的源代码创建的,它们随后由 Python/ceval.c 中的 PyEval_EvalFrameEx
执行。源代码有点像野兽,但最终就像一个简单的处理器一样工作,其中抽象了一些复杂的位。字节码是该处理器的汇编语言。特别是这个 "processor" 的 "opcodes" 之一是(恰当地命名)CALL_FUNCTION
。回调经过多次调用,最终到达 PyObject_Call()
。此函数采用指向 PyObject
的指针并从其类型中提取 tp_call
属性并直接调用它(从技术上讲,它首先检查它是否存在):
...
call = func->ob_type->tp_call //func is an arg of PyObject_Call() and is a pointer to a PyObject
...
result = (*call)(func, arg, kw);
任何实现 __call__
的对象都被赋予一个 tp_call
属性,并带有指向实际函数的指针。我相信这是由 Objects/typeobject.c:
slotdefs[]
定义处理的
FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
"__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
PyWrapperFlag_KEYWORDS)
函数的 __call__
方法本身在 cpython 实现中定义,它定义了 python VM 应该如何开始执行该函数的字节码以及数据应该如何返回(如果有)。当你给任意 class 一个 __call__
方法时,该属性是一个函数对象,它再次引用回 __call__
的 cpython 实现。因此,当您调用 "normal" 函数时,会引用 foo.__call__
。当您调用可调用的 class 时,self.__call__
等同于 foo
并且调用的实际 cpython 引用是 self.__call__.im_func.__call__
.
免责声明
对我来说,这是一段进入未知领域的旅程,我完全有可能歪曲了实施的一些细节。我主要取自this blog post on how python callables work under the hood, and some digging of my own through the python source code