q09*_*987 1 python python-3.x python-3.6
g_list = {}
def add_new_row(row_key, curr_value):
global g_list
curr_row = [None] * 2 # a local variable. I expected it to disappear after the function returns.
curr_row[0] = curr_value
curr_row[1] = curr_value * 2
if row_key not in g_list:
g_list[row_key] = []
g_list[row_key].append(curr_row)
curr_row[1] = 100 # just for test and illustrate the append by reference.
def main():
curr_v = 1
add_new_row(1, curr_v)
curr_v = 2
add_new_row(2, curr_v)
global g_list
print(g_list)
if __name__ == "__main__":
main()
Output:
{1: [[1, 100]], 2: [[2, 100]]}
Run Code Online (Sandbox Code Playgroud)
根据我的理解,in中的append操作g_list[row_key].append是按引用复制的,这就是为什么value curr_row[1]始终为的原因100。但是,即使返回函数后,我也不了解全局变量如何g_list包含本地分配列表(即curr_row)的值。
我希望使用以下行:
g_list[row_key].append(curr_row[:])
Run Code Online (Sandbox Code Playgroud)
但是以下代码有效:
g_list[row_key].append(curr_row)
Run Code Online (Sandbox Code Playgroud)
发生这种本地列表的深层复制时,有人可以给我一些建议吗?
您正在使用名称混淆对象。所有 Python对象都存在于堆中,名称只是对这些对象的引用。该列表从不复制,Python不必复制。
您可以将名称视为字符串上的标签,然后将这些标签与对象相关联。您可以根据需要使用任意数量的标签来引用给定对象。函数中的本地名称只是更多标签,而在函数退出时将清除标签。另一方面,对象将继续存在,直到不再附加标签。
出于这种类比的目的,列表的索引仅包含更多标签,而字典中的键充当相应值的标签。实际上,模块的全局变量实际上只是字典中的键值对!
因此,在您的特定示例中,您创建了一个引用None两次的list对象:
[None] * 2
Run Code Online (Sandbox Code Playgroud)
此时,此列表没有附加标签,它只是刚刚创建的。它不会立即消失的唯一原因是Python执行堆栈正在引用它。Python接下来执行的下一条指令是占据执行堆栈的顶部并将其分配给本地,因为该语句的其余部分是:
curr_row = ...
Run Code Online (Sandbox Code Playgroud)
因此,上面的列表对象现在带有一个标签,即local curr_now。
然后,您再创建一些值,并将其附加到列表中的索引中:
curr_row[0] = curr_value
curr_row[1] = curr_value * 2
Run Code Online (Sandbox Code Playgroud)
所以给出curr_value的1你现在有这样的:
[None] * 2
Run Code Online (Sandbox Code Playgroud)
(上面的图在某种程度上简化了这种情况,因为整数1和2确实也是独立的对象,但这会使上面的图不可读)。
接下来,您添加一个g_list具有新的空列表的密钥:row_key在1此处:
if row_key not in g_list:
g_list[row_key] = curr_row
Run Code Online (Sandbox Code Playgroud)
这将在字典和一个新的空列表对象之间创建一个引用:
curr_row = ...
Run Code Online (Sandbox Code Playgroud)
最后,您通过向其添加新索引来更改该空列表,因为您将引用的列表附加到该空列表上curr_row:
g_list[row_key].append(curr_row)
Run Code Online (Sandbox Code Playgroud)
情况变为:
curr_row[0] = curr_value
curr_row[1] = curr_value * 2
Run Code Online (Sandbox Code Playgroud)
请注意字典值如何引用列表,而字典值又使用1和引用列表2。下一行通过分配索引1来更改后一个列表:
curr_row[1] = 100
Run Code Online (Sandbox Code Playgroud)
将情况更改为:
+---------+ +---------+ +---------+
| Globals | | | Heap | | | Locals |
+---------+ | +---------+ | +---------+
| |
| |
g_list ------------> {} |
| |
| [1, 2] <----------- curr_row
| |
Run Code Online (Sandbox Code Playgroud)
最后,该函数退出,删除所有本地名称:
if row_key not in g_list:
g_list[row_key] = curr_row
Run Code Online (Sandbox Code Playgroud)
您在函数内部创建并引用为的列表curr_row 不会被删除,因为从字典作为值引用0的列表索引中仍然有对它的g_list引用。
Python无需在任何地方复制列表(深层或其他形式)。
附带说明:您不需要global g_list在函数中使用语句。仅当您想更改全局名称(标签)所连接的内容时,才需要该语句。您只需设置g_list 一次,即可将该标签与字典对象绑定在一起。从那里开始,标签仍然连接到同一词典。仅在需要global <name>从函数内部重新绑定这样的名称时才需要使用,并且仅因为否则 Python会将分配给您的名称视为本地名称。由于没有在函数中尝试更改g_list引用的内容,因此可以安全地global g_list从代码中删除语句,而无需更改代码的行为。