Skip to main content
 首页 » 编程设计

python之python何时复制添加到全局字典的本地列表

2025年05月04日16over140

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]]} 

根据我的理解,g_list[row_key].append 中的附加操作是按引用复制的,这就是为什么 curr_row[1] 的值是总是 100。但是,我不明白全局变量 g_list 如何包含本地分配列表的值(即 curr_row),即使在函数返回后也是如此。

我希望使用以下行:

g_list[row_key].append(curr_row[:]) 

但以下代码有效:

g_list[row_key].append(curr_row) 

当这个本地列表的深拷贝发生时,有人能给我一些建议吗?

请您参考如下方法:

您将对象 与名称 混淆了。 所有 Python 对象都存在于堆中,名称只是对这些对象的引用。该列表永远不会被复制,Python 也不需要。

您可以将名称视为一段字符串上的标签,并将这些标签绑定(bind)到对象上。您可以使用任意数量的标签来引用给定的对象。函数中的局部名称只是更多标签,函数退出时清理的是标签。另一方面,对象将继续存在,直到不再附加标签为止。

为了这个类比的目的,列表的索引只是更多的标签,而字典中的键充当相应值的标签。事实上,模块的全局变量实际上只是字典中的键值对!

因此,在您的特定示例中,您创建了一个引用 None 的列表对象, 两次:

[None] * 2 

此时,这个列表还没有附加标签,它只是刚刚创建的。它没有立即消失的唯一原因是 Python 执行堆栈正在引用它。 Python 然后执行的下一条指令是获取执行堆栈的顶部并将其分配给本地,因为该语句的其余部分是:

curr_row = ... 

所以上面的列表对象现在有一个标签,本地curr_now .

然后您再创建一些值并将它们附加到列表中的索引:

curr_row[0] = curr_value 
curr_row[1] = curr_value * 2 

所以给定一个 curr_value的 1你现在有这个:

+---------+          +---------+              +---------+   
| Globals |    |     |  Heap   |        |     | Locals  |   
+---------+    |     +---------+        |     +---------+   
               |                        |                   
               |                        |                   
 g_list ------------> {}                |                   
               |                        |                   
               |            [1, 2] <----------- curr_row    
               |                        |                   
 

(上图稍微简化了情况,因为整数 1 和 2 也是真正独立的对象,但这会使上图不可读)。

接下来,您将 key 添加到 g_list有一个新的空列表:row_key是1这里:

if row_key not in g_list: 
    g_list[row_key] = curr_row 

这会在字典和一个新的空列表对象之间创建一个引用:

+---------+          +---------+              +---------+ 
| Globals |    |     |  Heap   |       |      | Locals  | 
+---------+    |     +---------+       |      +---------+ 
               |                       |                  
               |                       |                  
 g_list -------------> {1: <value>}    |                  
               |             |         |                  
               |             |         |                  
               |             v         |                  
               |            []         |                  
               |                       |                  
               |                       |                  
               |                       |                  
               |          [1, 2] <------------- curr_row  
               |                       |                               

最后,您通过向其添加新索引来更改该空列表,因为您附加了 curr_row 引用的列表。对它:

g_list[row_key].append(curr_row) 

将情况更改为:

+---------+          +---------+              +---------+ 
| Globals |    |     |  Heap   |       |      | Locals  | 
+---------+    |     +---------+       |      +---------+ 
               |                       |                  
               |                       |                  
 g_list -------------> {1: <value>}    |                  
               |             |         |                  
               |             |         |                  
               |             v         |                  
               |           [<0>]       |                  
               |             |         |                  
               |             |         |                  
               |             v         |                  
               |          [1, 2] <------------- curr_row  
               |                       |                  
               |                       |                  

注意字典值是如何引用列表的,列表又用1引用列表。和 2在里面。下一行通过分配给索引 1 来更改后一个列表:

curr_row[1] = 100 

将情况改变为:

+---------+          +---------+              +---------+ 
| Globals |    |     |  Heap   |       |      | Locals  | 
+---------+    |     +---------+       |      +---------+ 
               |                       |                  
               |                       |                  
 g_list -------------> {1: <value>}    |                  
               |             |         |                  
               |             |         |                  
               |             v         |                  
               |           [<0>]       |                  
               |             |         |                  
               |             |         |                  
               |             v         |                  
               |         [1, 100]<------------- curr_row  
               |                       |                  
               |                       |                  

最后,函数退出,删除所有本地名称:

+---------+          +---------+     
| Globals |    |     |  Heap   |     
+---------+    |     +---------+     
               |                     
               |                     
 g_list -------------> {1: <value>}  
               |             |       
               |             |       
               |             v       
               |           [<0>]     
               |             |       
               |             |       
               |             v       
               |         [1, 100]    
               |                     
               |                     

您在函数内创建并引用为 curr_row 的列表未删除,因为索引 0 中仍有对它的引用g_list 列表中的字典被引用为一个值。

Python 无需在任何地方复制列表(深度或其他方式)。

附带说明:您不需要使用 global g_list函数中的语句。仅当您想更改全局名称(标签)所连接的内容时才需要该语句。你只设置 g_list 一次,将该标签绑定(bind)到字典对象。从那时起,该标签仍然连接到同一个字典。你只需要使用 global <name>如果您需要从函数内重新绑定(bind)这样的名称,并且只是因为 否则 Python 将您分配给的名称视为本地名称。因为你的函数没有尝试改变什么 g_list正在引用,您可以安全地删除 global g_list在不改变代码行为方式的情况下从您的代码中提取语句。