Kel*_*ndy 5 python cpython python-internals
大概两者mylist.reverse()和list.reverse(mylist)最终reverse_slice在listobject.cvialist_reverse_impl或 中执行PyList_Reverse。但他们实际上是如何到达那里的?从 Python 表达式到该 C 文件中的 C 代码的路径是什么?是什么将它们联系起来?它们经历了这两个反向函数中的哪一个(如果有的话)?
赏金更新:Dimitris 的回答(更新 2:我的意思是原始版本,现在扩展之前)及其下面的评论解释了部分内容,但我仍然缺少一些东西,希望看到一个全面的答案。
LOAD_METHOD,将澄清这一点。(正如 Dimitris 回答下的评论所做的那样。)list_reverse是listobject.c.h文件中的函数?我不认为 Python 解释器就像“让我们寻找一个听起来相似的文件和一个听起来相似的函数”。我宁愿怀疑该list类型是在某处定义的,并且以某种方式在名称“”下“注册” list,并且该reverse函数在名称“ reverse”下“注册”(也许这就是LIST_REVERSE_METHODDEF宏的作用?)。call_function)。真正让我感兴趣的是我最初所说的,从 Python 表达式到该 C 文件中的 C 代码的路径。最好是如何找到这样的路径。解释我的动机:对于另一个问题,我想知道当我调用list.reverse(mylist). 我相当有信心通过浏览和搜索名称找到了它。但我想更加确定,并且更好地理解这些联系。
PyList_Reverse 是 C-API 的一部分,如果您在 C 中操作 Python 列表,您会调用它,在这两种情况下都不会使用它。
这些都经过list_reverse_impl(实际上是list_reverse哪个包装list_reverse_impl),它是同时实现list.reverse和的C 函数list_instance.reverse。
这两个调用都由call_functionin处理,在为它们生成ceval的CALL_METHOD操作码执行后到达那里(dis.dis查看它的语句)。call_function在 Python 3.8 中进行了大量更改(引入了PEP 590),因此从那时起发生的事情可能是一个太大的主题,无法在单个问题中讨论。
附加问题:
来自两个python表达式的两条路径如何收敛?如果我理解正确,反汇编和讨论字节码以及堆栈会发生什么,特别是
LOAD_METHOD,将澄清这一点。
让我们在两个表达式编译为各自的字节码表示后开始:
l = [1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)
案例A,因为l.reverse()我们有:
1 0 LOAD_NAME 0 (l)
2 LOAD_METHOD 1 (reverse)
4 CALL_METHOD 0
6 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
情况 B,因为list.reverse(l)我们有:
1 0 LOAD_NAME 0 (list)
2 LOAD_METHOD 1 (reverse)
4 LOAD_NAME 2 (l)
6 CALL_METHOD 1
8 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
我们可以放心地忽略RETURN_VALUE操作码,这里并不重要。
让我们专注于每个操作代码的单独实现,即LOAD_NAME,LOAD_METHOD和CALL_METHOD。我们可以通过查看对它调用的操作来了解什么被推送到值堆栈上。(注意,它被初始化为指向位于每个表达式的框架对象内的值堆栈。)
在这种情况下执行的操作非常简单。给定我们的名字,l或者list在每种情况下,(每个名字都可以在 `co->co_names 中找到,一个存储我们在代码对象中使用的名字的元组),步骤是:
locals。如果找到,转4。globals。如果找到,转4。builtins。如果找到,转4。在情况 A 中,名称l可在全局变量中找到。在情况 B 中,它可以在内置函数中找到。所以,在 之后LOAD_NAME,堆栈看起来像:
案例一: stack_pointer -> [1, 2, 3, 4]
案例B: stack_pointer -> <type list>
首先,我不应该认为只有在执行属性访问(即obj.attr)时才会生成此操作码。您也可以获取一个方法并通过a = obj.attrthen调用它,a()但这会导致CALL_FUNCTION生成一个操作码(更多信息请参见下文)。
加载可调用对象的名称后(reverse在这两种情况下),我们都会在堆栈顶部的对象([1, 2, 3, 4]或list)中搜索名为 的方法reverse。这是通过_PyObject_GetMethod,其文档说明:
如果找到方法,则返回 1,如果它是来自
__dict__使用描述符协议的常规属性或返回的内容,则返回0 。
当我们reverse通过列表对象的实例访问属性 ( )时,只能在案例 A 中找到方法。在情况 B 中,在调用描述符协议后返回可调用对象,因此返回值为 0(但我们当然会取回对象!)。
在这里,我们对返回的值产生分歧:
情况 A:
SET_TOP(meth);
PUSH(obj); // self
Run Code Online (Sandbox Code Playgroud)
我们有一个SET_TOP后跟一个PUSH. 我们将方法移到堆栈顶部,然后再次压入该值。在这种情况下,stack_pointer现在看起来:
stack_pointer -> [1, 2, 3, 4]
<reverse method of lists>
Run Code Online (Sandbox Code Playgroud)
在情况 B 中,我们有:
SET_TOP(NULL);
Py_DECREF(obj);
PUSH(meth);
Run Code Online (Sandbox Code Playgroud)
再次 aSET_TOP后跟 a PUSH。obj(ie list)的引用计数减少了,因为据我所知,它不再需要了。在这种情况下,堆栈现在看起来像这样:
stack_pointer -> <reverse method of lists>
NULL
Run Code Online (Sandbox Code Playgroud)
对于情况 B,我们有一个额外的LOAD_NAME. 按照前面的步骤,案例 B 的堆栈现在变为:
stack_pointer -> [1, 2, 3, 4]
<reverse method of lists>
NULL
Run Code Online (Sandbox Code Playgroud)
很相似。
这不会对堆栈进行任何修改。这两种情况都会导致调用call_function传递线程状态、堆栈指针和位置参数 ( oparg)的数量。
唯一的区别在于用于传递位置参数的表达式。
对于案例 A,我们需要考虑self应该作为第一个位置参数插入的隐式。由于为它生成的操作码并不表示位置参数已被传递(因为没有明确传递):
4 CALL_METHOD 0
Run Code Online (Sandbox Code Playgroud)
我们调用call_functionwithoparg + 1 = 0 + 1 = 1来表示堆栈中存在一个位置参数 ( [1, 2, 3, 4])。
在情况 B 中,我们明确地将实例作为第一个参数传递,这是考虑到的:
6 CALL_METHOD 1
Run Code Online (Sandbox Code Playgroud)
所以调用call_function可以立即oparg作为位置参数的值传递。
什么是压入堆栈的“未绑定方法”?它是“C 函数”(哪个?)还是“Python 对象”?
它是一个围绕 C 函数的 Python 对象。Python 对象是一个方法描述符,它包装的 C 函数是list_reverse.
所有内置方法和函数都是用 C 实现的。在初始化期间,CPython 会初始化所有内置函数(参见list 此处)并为所有方法添加包装器。这些包装器(对象)是用于实现方法和函数的描述符。
当一个方法通过它的一个实例从一个类中检索出来时,它被称为绑定到那个实例。这可以通过查看__self__分配给它的属性来看到:
m = [1, 2, 3, 4].reverse
m() # use __self__
print(m.__self__) # [4, 3, 2, 1]
Run Code Online (Sandbox Code Playgroud)
即使没有限定它的实例,仍然可以调用此方法。它绑定到那个实例。(注意:这是由CALL_FUNCTION操作码处理的,而不是由操作码处理LOAD/CALL_METHOD的)。
未绑定方法是尚未绑定到实例的方法。list.reverse未绑定,它正在等待通过实例调用以绑定到它。
未绑定的东西并不意味着它不能被调用,list.reverse如果你self自己明确地将参数作为参数传递,它就可以被调用。请记住,方法只是self在绑定到实例后作为第一个参数隐式传递的特殊函数(除其他外)。
我怎么知道它
list_reverse是 listobject.ch 文件中的函数?
这很简单,您可以看到列表的方法在listobject.c. LIST_REVERSE_METHODDEF只是一个宏,当被替换时,将list_reverse函数添加到该列表中。然后tp_methods将列表的 包装在函数对象中,如前所述。
这里的事情可能看起来很复杂,因为 CPython 使用内部工具参数诊所来自动处理参数。这有点移动定义,稍微混淆。
| 归档时间: |
|
| 查看次数: |
378 次 |
| 最近记录: |