使用 id() 在内存中传递对象引用 Python Pass-By-Object-Reference

Python Pass-By-Object-Reference in memory using id()

提问人:matchaicedtea 提问时间:6/13/2023 更新时间:6/14/2023 访问量:59

问:

我正在尝试参考 Robert Heaton 的文章测试一些代码,该文章解释了将参数传递到函数的不同概念之间的区别。

为什么 list 和 myList 存储在内存中的同一位置,而根据 Heaton 的说法,它们应该是两个完全独立的变量。

这是我写的:

def main():
    myList = [0]
    print(f"myList: {id(myList)}")
    print(f"[0]: {id([0])}")
    append(myList)
    print(f"After call: {myList}")


def append(list):
    print(f"append's list: {id(list)}")
    list.append(1)

根据 Heaton 的说法,通过按对象引用传递,“函数提供了自己的框并为自己创建了一个新变量”。因此,我希望 id(myList) 和 id(list) 应该是存储在内存中不同位置的两个不同变量。

但是,我收到的输出是:

myList: 1941960670528
[0]: 1941963219200
append's list: 1941960670528
After call: [0, 1]
python-3.x 参数传递

评论

0赞 chepner 6/13/2023
阅读 nedbatchelder.com/text/names.html。Python 变量是对象的名称,而不是内存中的命名位置。对象可以(并且经常)由多个名称引用。

答:

2赞 matszwecja 6/13/2023 #1

你必须考虑整个段落。这篇文章并不是说它应该是 2 个不同的对象。有一个列表,但单独的变量指向它。

请考虑以下几点 (变量更改为,因为内置变量不应用于变量名称。listl

def append(l):
    print(f"append's list: {id(l)}")
    l.append(1)
    l = []
    print(f"append's list after reassigning: {id(l)}")

myList = [0]
print(f"myList: {id(myList)}")
append(myList)
print(f"After call: {myList}")
print(f"myList id after call: {id(myList)}")

我们重新分配到一个新列表,但外部仍然指向包含lmyList[0, 1]

它本质上试图展示的是,一个变量有 3 个部分——名称、引用及其值。名称 - 例如 , - 是我们在代码中引用的名称。它们又被解析为一个引用 - 简化,给出的值 - 在内存中找到一个存储实际的位置。 因此,当我们将列表传递给函数时,相同的引用会被赋予一个我们可以在函数内部使用的新名称。更改与此引用相关的值将对外部函数可见,因为引用是相同的。如果我们做了一些改变引用的事情,例如用 分配一个新列表,这不会影响外部列表,因为它仍然使用旧的引用。lmyListid()l = []

总而言之,我们有 2 个关系:name -> reference 和 reference -> value

调用函数时,我们给引用一个新名称,结果如下:

name_outer -\
             >- reference -- value
name_inner -/

更改该值会导致 像这样:

name_outer -\
             >- reference -- new_value
name_inner -/

在更改引用结果时:

name_outer  --  reference   --   value

name_inner -- new_reference -- new_value

评论

0赞 matchaicedtea 6/13/2023
为什么如果我 id() 变量名称和 id() 值,它们会给出两个不同的引用?
0赞 matszwecja 6/13/2023
因为它们 2 个不同的引用 - 每次创建新对象时,它都会获得一个唯一的引用,无论它是否与现有对象共享一个值(s 和 s 有小的例外以节省内存,但它们只是例外) 事实上,创建一个具有自己引用的新列表, 但没有给它任何名字。你不是在ing值,你是在ing恰好与另一个变量具有相同值的对象。stringintid([])idid
0赞 jthulhu 6/14/2023 #2

据我所知(但也许我被教过不同的术语),当这篇文章说 Python 有一个特殊的函数调用模式时,它是错误的。Python 使用按值调用,这仅仅意味着(据我所知)函数调用将值的位复制到形式参数的值中(即在寄存器或堆栈中)。f(x)xf

使其行为如描述的那样是 Python 值实际上是指向包含实际对象的内存区域的指针。变量对应于一个内存位置(例如,它可以是“寄存器”,或者“地址指向的位置上方 7 个字节”),它指示指针的实际位置。rcxrbp

请注意,此行为与其他隐藏指针和内存管理的语言(如 Java、OCaml 或 Smalltalk)相同。即使使用这种编译模式,值也并不总是指向存储实际数据的内存区域的指针:事实上,如果我们考虑与指针大小相同的不可变数据,人们可能会忍不住不为该数据在堆上的某个地方分配机器字。这可能会使 GC 逻辑变得相当复杂,但例如在 OCaml 中为整数实现。

关于你的例子中发生的事情的@matszwecja解释对我来说似乎是正确的,所以我不会在这里重复。

此外,作为旁注,Python 的规范并没有告诉它泄漏了变量值所指向的地址。只是当前的实现是这样做的,因为它满足规范,但这在 Jython 或 PyPy 中不起作用。id