为什么列表变量有时不受函数变化的影响,因为我认为 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?
对于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]
案例三:为什么函数调用后列表不为空?
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:完全按预期工作,但请注意,我已经 return从函数中编辑了所有三个列表。
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:如果我不显式地从函数调用中 return 为什么不影响函数外部的变量?
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
,并且对旧对象的引用被丢弃。当它是最后一个引用时,这会导致对象被清理;在你的例子中,参数 used 给一个对象起别名也绑定到调用者的一个名字,但是在重新绑定之后,别名关联就丢失了。
除了 C/C++ 人员:重新绑定就像分配给指针变量,例如int *px = pfoo;
(初始绑定),随后是 px = pbar;
(重新绑定),其中 pfoo
和 pbar
本身都是指向 int
的指针。当 px = pbar;
赋值发生时,px
以前指向与 pfoo
相同的东西并不重要,它现在指向新的东西,并且跟进 [=23] =](突变,不重新绑定)只影响 pbar
指向的内容,pfoo
的目标不变。
相比之下,突变 不会破坏别名关联,因此:
name[1] = val
会重新绑定 name[1]
本身,但不会重新绑定 name
;它继续像以前一样引用同一个对象,它只是在适当的地方改变那个对象,保持所有别名不变(所以所有别名同一个对象的名字都会看到变化的结果)。
对于您的具体情况,您可以通过更改为切片 assignment/deletion 或其他形式的就地突变,将“损坏的”函数从重新绑定更改为别名,例如:
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,我原本需要从一个列表中提取奇数和偶数位置并分配给新列表,然后清除原始列表。我认为列表受到通过“按引用传递”的函数调用的影响。测试一些场景,它有时会起作用。有人可以解释一下 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]
案例三:为什么函数调用后列表不为空?
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:完全按预期工作,但请注意,我已经 return从函数中编辑了所有三个列表。
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:如果我不显式地从函数调用中 return 为什么不影响函数外部的变量?
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
,并且对旧对象的引用被丢弃。当它是最后一个引用时,这会导致对象被清理;在你的例子中,参数 used 给一个对象起别名也绑定到调用者的一个名字,但是在重新绑定之后,别名关联就丢失了。
除了 C/C++ 人员:重新绑定就像分配给指针变量,例如int *px = pfoo;
(初始绑定),随后是 px = pbar;
(重新绑定),其中 pfoo
和 pbar
本身都是指向 int
的指针。当 px = pbar;
赋值发生时,px
以前指向与 pfoo
相同的东西并不重要,它现在指向新的东西,并且跟进 [=23] =](突变,不重新绑定)只影响 pbar
指向的内容,pfoo
的目标不变。
相比之下,突变 不会破坏别名关联,因此:
name[1] = val
会重新绑定 name[1]
本身,但不会重新绑定 name
;它继续像以前一样引用同一个对象,它只是在适当的地方改变那个对象,保持所有别名不变(所以所有别名同一个对象的名字都会看到变化的结果)。
对于您的具体情况,您可以通过更改为切片 assignment/deletion 或其他形式的就地突变,将“损坏的”函数从重新绑定更改为别名,例如:
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()