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在不改变代码行为方式的情况下从您的代码中提取语句。
