gpt4 book ai didi

python - mylist.reverse() 和 list.reverse(mylist) 是如何执行的?

转载 作者:行者123 更新时间:2023-12-04 11:23:51 27 4
gpt4 key购买 nike

大概都是 mylist.reverse()list.reverse(mylist)最终执行 reverse_slice listobject.c通过 list_reverse_impl PyList_Reverse .但他们实际上是如何到达那里的?从 Python 表达式到该 C 文件中的 C 代码的路径是什么?是什么将它们联系起来?它们经历了这两个反向函数中的哪一个(如果有的话)?

更新 赏金:Dimitris 的回答(更新 2:我指的是原始版本,现在扩展之前)及其下面的评论解释了部分内容,但我仍然缺少一些东西,希望看到一个全面的答案。

  • 来自两个 Python 表达式的两条路径如何收敛?如果我理解正确,反汇编和讨论字节码以及堆栈会发生什么,特别是 LOAD_METHOD ,将澄清这一点。 (正如 Dimitris 回答下的评论所做的那样。)
  • 什么是压入堆栈的“未绑定(bind)方法”?它是“C 函数”(哪个?)还是“Python 对象”?
  • 我怎么知道它是 list_reverse listobject.c.h 中的函数文件?我不认为 Python 解释器就像“让我们寻找一个听起来相似的文件和一个听起来相似的函数”。我比较怀疑 list类型在某处被定义并且以某种方式以“list”的名称“注册”,而reverse函数以“reverse”的名称“注册”(也许这就是 LIST_REVERSE_METHODDEF 宏的作用?)。
  • 我对(对于这个问题)堆栈帧、参数处理和类似的东西不感兴趣(所以可能没有太多内容 call_function )。真正让我感兴趣的是我最初所说的,从 Python 表达式到该 C 文件中的 C 代码的路径。最好是如何找到这样的路径。

  • 解释我的动机:对于 another question当我拨打 list.reverse(mylist) 时,我想知道 C 代码做了什么工作.我相当有信心通过浏览和搜索名称找到了它。但我想更加确定,并且更好地理解这些联系。

    最佳答案

    PyList_Reverse是 C-API 的一部分,如果您在 C 中操作 Python 列表,您会调用它,在这两种情况下都不会使用它。

    这些都经过 list_reverse_impl (实际上 list_reverse 包装了 list_reverse_impl )这是实现 list.reverse 的 C 函数和 list_instance.reverse .

    这两个调用都由 call_function 处理在 ceval , 在 CALL_METHOD 之后到达那里为它们生成的操作码被执行(dis.dis 查看它的语句)。 call_function Python 3.8 中发生了很多变化(引入了 PEP 590 ),所以从那以后发生的事情可能是一个太大的主题,无法在一个问题中讨论。

    其他问题:

    How do the two paths from the two python expressions converge? If I understand things correctly, disassembling and discussing the byte code and what happens to the stack, particularly LOAD_METHOD, would clarify this.



    让我们在两个表达式编译为各自的字节码表示后开始:
    l = [1, 2, 3, 4]

    案例 A,适用于 l.reverse()我们有:
    1           0 LOAD_NAME                0 (l)
    2 LOAD_METHOD 1 (reverse)
    4 CALL_METHOD 0
    6 RETURN_VALUE

    案例 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

    我们可以放心地忽略 RETURN_VALUE操作码,在这里并不重要。

    让我们关注每个操作码的单独实现,即 LOAD_NAME , LOAD_METHODCALL_METHOD .我们可以看到什么被推送到 value stack通过查看什么 operations被称为它。 (注意,它被初始化为指向位于每个表达式的框架对象内的值堆栈。)

    LOAD_NAME :

    在这种情况下执行的操作非常简单。鉴于我们的名字, llist在每种情况下,(每个名称都可以在 `co->co_names 中找到,这是一个存储我们在代码对象中使用的名称的元组)步骤是:
  • 寻找里面的名字 locals .如果找到,请转至 4。
  • 寻找里面的名字 globals .如果找到,请转至 4。
  • 寻找里面的名字 builtins .如果找到,请转至 4。
  • 如果找到,则将名称表示的值压入堆栈。否则,名称错误。

  • 在案例 A 中,姓名 l可以在全局变量中找到。在情况 B 中,它可以在内置函数中找到。所以,在 LOAD_NAME 之后,堆栈看起来像:

    案例A: stack_pointer -> [1, 2, 3, 4]
    案例B: stack_pointer -> <type list>
    LOAD_METHOD :

    首先,我不应该认为只有在执行属性访问(即 obj.attr )时才会生成此操作码。你也可以获取一个方法并通过 a = obj.attr 调用它然后 a()但这会导致 CALL_FUNCTION生成的操作码(更多信息请参见进一步)。

    加载可调用对象的名称(两种情况下都是 reverse)后,我们搜索 object on the top of the stack ( [1, 2, 3, 4]list )用于名为 reverse 的方法.这是通过 _PyObject_GetMethod 完成的,其文档说明:

    Return 1 if a method is found, 0 if it's a regular attribute from __dict__ or something returned by using a descriptor protocol.



    当我们通过列表对象的实例访问属性( reverse )时,只能在案例 A 中找到方法。在情况 B 中,在调用描述符协议(protocol)后返回可调用对象,因此返回值为 0(但我们当然会取回对象!)。

    在这里,我们对返回的值产生分歧:

    情况 A:
    SET_TOP(meth);
    PUSH(obj); // self

    我们有一个 SET_TOP后跟一个 PUSH .我们将方法移到堆栈顶部,然后再次压入该值。在这种情况下, stack_pointer现在看起来:
    stack_pointer -> [1, 2, 3, 4]
    <reverse method of lists>

    在情况 B 中,我们有:
    SET_TOP(NULL);
    Py_DECREF(obj);
    PUSH(meth);

    又是一个 SET_TOP后跟一个 PUSH . obj的引用计数(即 list )减少了,因为据我所知,它不再需要了。在这种情况下,堆栈现在看起来像这样:
    stack_pointer -> <reverse method of lists>
    NULL

    对于情况 B,我们有一个额外的 LOAD_NAME .按照前面的步骤,案例 B 的堆栈现在变为:
    stack_pointer -> [1, 2, 3, 4]
    <reverse method of lists>
    NULL

    很相似。

    CALL_METHOD :

    这不会对堆栈进行任何修改。这两种情况都会导致拨打 call_function传递线程状态、堆栈指针和位置参数的数量( oparg )。

    唯一的区别在于用于传递位置参数的表达式。

    对于案例 A,我们需要考虑隐含的 self应该作为第一个位置参数插入。由于为它生成的操作码并不表示位置参数已被传递(因为没有明确传递):
    4 CALL_METHOD              0

    我们调用 call_functionoparg + 1 = 0 + 1 = 1表示堆栈中存在一个位置参数( [1, 2, 3, 4 ])。

    在情况 B 中,我们明确地将实例作为第一个参数传递,这是考虑到的:
    6 CALL_METHOD              1

    所以拨打 call_function可以立即通过 oparg作为位置参数的值。

    What is the "unbound method" pushed onto the stack? Is it a "C function" (which one?) or a "Python object"?



    它是一个围绕 C 函数的 Python 对象。 Python 对象是一个方法描述符,它包装的 C 函数是 list_reverse .

    所有内置方法和函数都是用 C 实现的。在初始化过程中,CPython initializes所有内置函数(参见 list here )并在所有 methods 周围添加包装器.这些包装器(对象)是用于实现 Methods and Functions 的描述符。 .

    当一个方法通过它的一个实例从一个类中检索出来时,它被称为绑定(bind)到那个实例。这可以通过查看 __self__ 看到分配给它的属性:
    m = [1, 2, 3, 4].reverse
    m() # use __self__
    print(m.__self__) # [4, 3, 2, 1]

    即使没有限定它的实例,仍然可以调用此方法。它绑定(bind)到那个实例。 (注意:这是由 CALL_FUNCTION 操作码处理的,而不是由 LOAD/CALL_METHOD 处理的)。

    未绑定(bind)方法是尚未绑定(bind)到实例的方法。 list.reverse未绑定(bind),它正在等待通过实例调用以绑定(bind)到它。

    未绑定(bind)的东西不代表不能调用, list.reverse如果您明确传递 self ,则调用就好了把自己当作一个论据来论证。请记住,方法只是特殊的函数,(除其他外)隐式传递 self作为绑定(bind)到实例后的第一个参数。

    How can I tell that it's the list_reverse function in the listobject.c.h file?



    这很简单,你可以看到列表的方法在 listobject.c 中被初始化。 . LIST_REVERSE_METHODDEF只是一个宏,当被替换时,添加 list_reverse功能到该列表。 tp_methods然后如前所述将列表的 包裹在函数对象中。

    这里的事情可能看起来很复杂,因为 CPython 使用了一个内部工具, argument clinic , 自动处理参数。这有点移动定义,稍微混淆。

    关于python - mylist.reverse() 和 list.reverse(mylist) 是如何执行的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60682797/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com