列表中的 Python 3 奇怪函数行为

问题描述 投票:0回答:3

所以我正在 python 3.3 中使用列表,这是我的示例代码:

def change_to_z(lis):
    lis[3] = 'z'

def change_to_k(lis):
    lis[4] = 'k'


def split(lis):
    lis = lis[3:] + lis[:3]

totest = ['a', 'b', 'c', 'd', 'e', 'f']

change_to_z(totest)
print(totest)
change_to_k(totest)
print(totest)
split(totest)
print(totest)

和输出:

['a', 'b', 'c', 'z', 'e', 'f']
['a', 'b', 'c', 'z', 'k', 'f']
['a', 'b', 'c', 'z', 'k', 'f']

请注意,当我调用前两个函数时,我能够修改列表,而 totest 始终引用该列表,即使它已更改。

但是,使用第三个函数时,变量 totest 不再引用列表的最新修改版本。我的调试器告诉我,在函数“split”内,列表被翻转,但在函数之外,它没有翻转。为什么变量名不再引用列表了?

为什么会出现这种情况?哪些运营商会发生这种情况?为什么有时变量名在函数中修改后仍然引用列表,但与其他运算符的行为不同?

python python-3.x scope
3个回答
1
投票

你对函数的

scope
的理解是错误的。

如果您确实想更改提供的列表,您可以尝试以下两种方法之一。

def split(lis):
    global lis
    lis = lis[3:] + lis[:3]

或者更好

def split(lis):
    lis[:] = lis[3:] + lis[:3] # Credits go to drewk for pointing this out

当你做的时候

def split(lis):
    lis = lis[3:] + lis[:3]

split
函数中,您正在创建一个 本地列表,其标识符与所提供的列表的标识符相同。但是,这两者不会冲突,因为传递的列表是global

如果您想尝试一下并了解它们是否真的不同,请使用

id()

lis = [1, 2, 3]
def split(lis):
    lis = lis[3:] + lis[:3]
    print id(lis)
split(lis)
print id(lis)

上述代码的输出

40785920
40785600

注意内存位置有何不同


1
投票

lis
的赋值是局部变量,不会更新列表。这在程序的python辅导可视化中很容易看到。

此外,请参阅这篇博文,了解正在发生的事情的清晰解释。这是一个简短的回顾:

  • 名称指的是值。
  • 许多名称可以引用一个值。
  • 名称的重新分配独立于其他名称。
  • 赋值永远不会复制数据。
  • 通过其所有名称都可以看到值的变化。
  • Python 以不同的方式分配可变值和不可变值。
  • 参考文献不仅仅是名称。

在您的代码中,使用 global 声明来查看它与默认本地分配的工作方式有何不同:

def split(lis):
    global lis

    lis = lis[3:] + lis[:3]

0
投票

您可以使用模块memory_graph:

https://pypi.org/project/memory-graph/

绘制数据图表并轻松查看变量之间共享哪些数据,以便更好地理解为什么

totest
没有更改。仅打印数据不会显示共享的数据。

import memory_graph

def change_to_z(lis):
    lis[3] = 'z'

def change_to_k(lis):
    lis[4] = 'k'

def split(lis):
    memory_graph.show(memory_graph.get_call_stack(), block=True) # <--- graph1
    lis = lis[3:] + lis[:3] # <--- assigns 'lis' a new list
    memory_graph.show(memory_graph.get_call_stack()) # <--- graph2

totest = ['a', 'b', 'c', 'd', 'e', 'f']

change_to_z(totest)
print(totest)
change_to_k(totest)
print(totest)
split(totest)
print(totest)

调用

split(lis)
时,首先
lis
函数中的
split()
变量和
totest
变量共享数据,如图 1 所示:

但是随后您为

lis
分配了一个新值,这使得
lis
变量引用新列表,但这不会更改
totest
变量,如图 2 所示:

没有简单的方法可以在 Python 中使用 call-by-reference 以便

split()
将新列表也分配给
totest
,因此最好寻找不同的方法。但如果您确实需要,您可以将
totest
包装在另一个列表中,并使用
[0]
索引在任何地方访问它。现在代码变得很难阅读,所以不建议这样做!

import memory_graph

def change_to_z(lis):
    lis[0][3] = 'z'

def change_to_k(lis):
    lis[0][4] = 'k'

def split(lis):
    memory_graph.show(memory_graph.get_call_stack(), block=True)
    lis[0] = lis[0][3:] + lis[0][:3] # <--- assigns 'lis[0]' a new list
    memory_graph.show(memory_graph.get_call_stack())
    
totest = [ ['a', 'b', 'c', 'd', 'e', 'f'] ] # <--- wrap in another list

change_to_z(totest)
print(totest[0])
change_to_k(totest)
print(totest[0])
split(totest)
print(totest[0])

如果我们绘制该程序的执行情况,我们首先会看到:

分配后我们看到:

并且

totest
变量现在已根据需要更新。

完全披露:我是memory_graph的开发者。

© www.soinside.com 2019 - 2024. All rights reserved.