为什么列表变量有时不受函数更改的影响,因为我认为 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?

查看:63
本文介绍了为什么列表变量有时不受函数更改的影响,因为我认为 python3 可以通过引用传递列表变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

For python3, I originally needed to extract odd and even positions from a list and assign it to new lists, then clear the original list. I thought lists were impacted by a function call through "pass by reference". Testing some scenarios, it works sometime. Could someone please explain how exactly python3 works here?

案例 1:空列表按预期填充了字符串.

Case 1: empty list is populated with string as expected.

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:中间列表元素按预期替换为字符串.

Case 2: middle list element is replaced with string as expected.

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:为什么函数调用后列表不为空?

Case 3: why is the list not empty after function call?

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:完全按预期工作,但请注意,我已从函数中返回了所有三个列表.

Case 4: working exactly as expected, but note I have returned all three lists from function.

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:如果我没有从函数调用中显式返回,为什么对函数外的变量没有影响?

Case 5: why no impact on the variables outside the function if I do not explicitly return from function call?

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

谢谢.

推荐答案

您的最终问题是将(就地)变异重新绑定混淆(也称为较少准确地说是重新分配").

Your ultimate problem is confusing (in-place) mutation with rebinding (also referred to somewhat less precisely as "reassignment").

在更改在函数外部不可见的所有情况下,您回滚函数内部的名称.当你这样做时:

In all the cases where the change isn't visible outside the function, you rebound the name inside the function. When you do:

name = val

name 中以前的内容无关紧要;它反弹val,并且对旧对象的引用被丢弃.当它是最后一个引用时,这会导致对象被清理;在您的情况下,参数 used 用于别名的对象也绑定到调用者中的名称,但在重新绑定后,该别名关联丢失.

it does not matter what used to be in name; it's rebound to val, and the reference to the old object is thrown away. When it's the last reference, this leads to the object being cleaned up; in your case, the argument used to alias an object also bound to a name in the caller, but after rebinding, that aliasing association is lost.

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

Aside for C/C++ folks: Rebinding is like assigning to a pointer variable, e.g. int *px = pfoo; (initial binding), followed later by px = pbar; (rebinding), where both pfoo and pbar are themselves pointers to int. When the px = pbar; assignment occurs, it doesn't matter that px used to point to the same thing as pfoo, it points to something new now, and following it up with *px = 1; (mutation, not rebinding) only affects whatever pbar points to, leaving the target of pfoo unchanged.

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

By contrast, mutation doesn't break aliasing associations, so:

name[1] = val

会重新绑定name[1]本身,但不会重新绑定name;它继续像以前一样引用同一个对象,它只是在原地改变那个对象,保留所有别名不变(因此所有别名同一个对象的名称都会看到更改的结果).

does rebind name[1] itself, but it doesn't rebind name; it continues to refer to the same object as before, it just mutates that object in place, leaving all aliasing intact (so all names aliasing the same object see the result of the change).

对于您的具体情况,您可以更改broken"通过更改为切片分配/删除或其他形式的就地突变来实现从重新绑定到别名的功能,例如:

For your specific case, you could change the "broken" functions from rebinding to aliasing by changing to slice assignment/deletion or other forms of in-place mutation, e.g.:

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 可以通过引用传递列表变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆