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

对于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 = []

谢谢你。

回答

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

在所有地方的变化是不可见的功能外情况下,反弹函数内部的名称。当你这样做时:

name = val

过去是什么并不重要name;它被反弹val,并且对旧对象的引用被丢弃。当它是最后一个引用时,这会导致对象被清理;在您的情况下,用于别名对象的参数也绑定到调用者中的名称,但在重新绑定后,别名关联丢失。

对于 C/C++ 人员来说:重新绑定就像分配给一个指针变量,例如int *px = pfoo;(初始绑定),然后是px = pbar;(重新绑定),其中pfoopbar本身都是指向int. 当px = pbar;赋值发生时,px过去指向与 相同的东西并不重要pfoo,它现在指向新的东西,并且跟随它*px = 1;(变异,而不是重新绑定)只会影响pbar指向的任何东西,而目标pfoo保持不变。

相比之下,变异不会破坏别名关联,因此:

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()


以上是为什么列表变量有时不受函数更改的影响,因为我认为python3可以通过引用传递列表变量?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>