为什么列表变量有时不受函数更改的影响,因为我认为 python3 可以通过引用列表变量进行传递?

Why is a list variable sometimes not impacted by changes in function as I thought python3 works on pass by reference with list variables?

提问人:rbewoor 提问时间:1/29/2021 更新时间:1/29/2021 访问量:484

问:

对于python3,我最初需要从列表中提取奇数和偶数位置并将其分配给新列表,然后清除原始列表。我以为列表受到通过“按引用传递”的函数调用的影响。测试一些场景,它有时会起作用。有人可以解释一下 python3 在这里究竟是如何工作的吗?

情况 1:空列表按预期填充字符串。

def func1(_in):
    _in.append('abc')

mylist = list()
print(f"Before:\nmylist = {mylist}")
func1(mylist)
print(f"After:\nmylist = {mylist}")

输出情况 1:

Before:
mylist = []
After:
mylist = ['abc']

情况 2:中间列表元素按预期替换为字符串。

def func2(_in):
    _in[1] = 'abc'

mylist = list(range(3))
print(f"Before:\nmylist = {mylist}")
func2(mylist)
print(f"After:\nmylist = {mylist}")

输出情况 2:

Before:
mylist = [0, 1, 2]
After:
mylist = [0, 'abc', 2]

案例3:为什么函数调用后列表不为空?

def func3(_in):
    _in = list()

mylist = list(range(3))
print(f"Before:\nmylist = {mylist}")
func3(mylist)
print(f"After:\nmylist = {mylist}")

输出情况 3:

Before:
mylist = [0, 1, 2]
After:
mylist = [0, 1, 2]

情况 4:完全按预期工作,但请注意,我已经从函数中返回了所有三个列表。

def func4_with_ret(_src, _dest1, _dest2):
    _dest1 = [val for val in _src[0:len(_src):2]]
    _dest2 = [val for val in _src[1:len(_src):2]]
    _src = list()
    return _src, _dest1, _dest2

source = list(range(6))
evens, odds = list(), list()
print(f"Before function call:\nsource = {source}\nevens = {evens}\nodds = {odds}")
source, evens, odds = func4_with_ret(source, evens, odds)
print(f"\nAfter function call:\nsource = {source}\nevens = {evens}\nodds = {odds}")

输出情况 4:

Before function call:
source = [0, 1, 2, 3, 4, 5]
evens = []
odds = []

After function call:
source = []
evens = [0, 2, 4]
odds = [1, 3, 5]

案例 5:如果我不显式地从函数调用返回,为什么对函数外部的变量没有影响?

def func5_no_ret(_src, _dest1, _dest2):
    _dest1 = [val for val in _src[0:len(_src):2]]
    _dest2 = [val for val in _src[1:len(_src):2]]
    _src = list()

source = list(range(6))
evens, odds = list(), list()
print(f"Before function call:\nsource = {source}\nevens = {evens}\nodds = {odds}")
func5_no_ret(source, evens, odds)
print(f"\nAfter function call:\nsource = {source}\nevens = {evens}\nodds = {odds}")

输出情况 5:

Before function call:
source = [0, 1, 2, 3, 4, 5]
evens = []
odds = []

After function call:
source = [0, 1, 2, 3, 4, 5]
evens = []
odds = []

谢谢。

python-3.x list 传递引用函数 调用 空列表

评论

1赞 Thymen 1/29/2021
简而言之,您问的是局部变量和全局变量是如何分配的。要点是,当您执行新赋值时,例如 将是本地的(仅在函数中),您将创建一个新对象。如果使用 ,则更改将是全局的,并导致一个空列表。同样,是新赋值(仅限本地赋值),并且是全局变体。_in = list()_in_in_in.clear()_dest1_dest1.extend([val for val in _src[0:len(_src):2]])
0赞 Jens Munk 1/29/2021
是的,在情况 1 和 2 中,您修改列表中的内容,这将传播。
2赞 ShadowRanger 1/29/2021
@Thymen:这与局部与全局无关(在这种情况下,这些是具有误导性的范围术语;接收到的可能不是全局的,即使它在示例代码中也是如此)。 在此代码中,它本身始终是本地的,但有时它会对调用方提供的对象进行别名化(当输入函数时,这始终为 true),但它可以反弹到不同的对象(该对象可能是也可能不是新创建的; 会将其别名化为与全局对象相同的对象,但仍保持本地)。真正的问题是“突变”与“重新绑定”/“重新分配”,与范围无关。list_in_in_in = some_global_in
1赞 juanpa.arrivillaga 1/29/2021
“我以为python3可以与列表变量一起通过引用传递?”不可以。Python 从来不是按引用调用的,也不是按值调用的。此外,评估策略从不依赖于对象的类型。您在这里看到的是,您的函数要么改变列表对象,例如 或者,或者它们不改变列表对象,.那只是分配,分配永远不会改变。退房: nedbatchelder.com/text/names.html_in.append_in[i] = x_in = list()

答:

6赞 ShadowRanger 1/29/2021 #1

您的最终问题是将(就地)突变与重新结合(也称为“重新分配”)混淆。

如果更改在函数外部不可见,则可以在函数内部重新显示名称。当您执行以下操作时:

name = val

过去是什么并不重要;它反弹到 ,并且对旧对象的引用被扔掉了。当它是最后一个引用时,这会导致对象被清理;在本例中,用于为对象增别的参数也绑定到调用方中的名称,但在重新绑定后,该别名关联将丢失。nameval

除了 C/C++ 的人:重新绑定就像分配给指针变量,例如 (初始绑定),然后是 (重新绑定),其中 和 本身都指向 。当赋值发生时,过去指向的事物与现在指向新事物的事物无关紧要,并且紧随其后(突变,而不是重新绑定)只会影响指向的任何内容,而目标保持不变。int *px = pfoo;px = pbar;pfoopbarintpx = pbar;pxpfoo*px = 1;pbarpfoo

相比之下,突变不会破坏混叠关联,因此:

name[1] = val

确实会重新绑定自己,但不会重新绑定;它继续像以前一样引用相同的对象,它只是在原地改变该对象,保持所有别名完好无损(因此所有别名相同对象的名称都会看到更改的结果)。name[1]name

对于您的具体情况,您可以通过更改为切片分配/删除或其他形式的就地突变,将“损坏”的函数从重新绑定更改为别名,例如:

def func3(_in):
    # _in = list()  BAD, rebinds
    _in.clear()     # Good, method mutates in place
    del _in[:]      # Good, equivalent to clear
    _in[:] = list() # Acceptable; needlessly creates empty list, but closest to original
                    # code, and has same effect

def func5_no_ret(_src, _dest1, _dest2):
    # BAD, all rebinding to new lists, not changing contents of original lists
    #_dest1 = [val for val in _src[0:len(_src):2]]
    #_dest2 = [val for val in _src[1:len(_src):2]]
    #_src = list()

    # Acceptable (you should just use multiple return values, not modify caller arguments)
    # this isn't C where multiple returns are a PITA
    _dest1[:] = _src[::2]  # Removed slice components where defaults equivalent
    _dest2[:] = _src[1::2] # and dropped pointless listcomp; if _src might not be a list
                           # list(_src[::2]) is still better than no-op listcomp
    _src.clear()

    # Best (though clearing _src is still weird)
    retval = _src[::2], _src[1::2]
    _src.clear()
    return retval

    # Perhaps overly clever to avoid named temporary:
    try:
        return _src[::2], _src[1::2]
    finally:
        _src.clear()

评论

0赞 rbewoor 1/29/2021
感谢您的详尽回答。从你的例子中学到了很多东西!
0赞 ShadowRanger 1/29/2021
@rbewoor:很高兴我能帮上忙。如果您的问题得到完全回答,请点击投票箭头下方的复选框接受答案吗?不要着急(如果其他人提供了更好的答案,请给他们支票),但这是表明您的问题已完全解决的好方法。
0赞 rbewoor 1/29/2021
做完了,先生!谢谢你告诉我复选标记。