Python: pass-by-reference
再次提出这个问题是因为遇到了一个 “想在 function 中修改 list 内容” 的场景,而正确的写法应该是用 slice-then-assign:
def test1(lst):
    lst = [999, 1000]
def test2(lst):
    lst[:] = [999, 1000]
if __name__ == "__main__":
    lst = [1, 2]
    
    test1(lst)
    print(lst)
    test2(lst)
    print(lst)
# Output:
#   [1, 2]
#   [999, 1000]
函数体内的 lst 只是 argument 的一个 copy,而这里 argument 是一个 reference,指向一个 list;而你对这个 reference copy 赋值,相当于让它指向一个新的 list,原来的 reference 没有变动。所以 test1 的逻辑大约是:
if __name__ == "__main__":
    lst = [1, 2]
    ENTER test1:
        lst_copy = lst  # => [1, 2]
        lst_copy = [999, 1000]
    EXIT test1
    print(lst)
# Output
#   [1, 2]
而 test2 的先 slice 再 assignment 实际上是调用了 __setitem__。根据 How assignment works with python list slice:
- If the left side of assignment is subscription, Python will call 
__setitem__on that object.a[i] = xis equivalent toa.__setitem__(i, x). - If the left side of assignment is slice, Python will also call 
__setitem__, but with different arguments:a[1:4]=[1,2,3]is equivalent toa.__setitem__(slice(1,4,None), [1,2,3]) 
所以即是 caller 是 reference copy,但它能实际影响 reference 指向的值。test2 的逻辑大约是:
if __name__ == "__main__":
    lst = [1, 2]
    ENTER test2:
        lst_copy = lst  # => [1, 2]
        lst_copy.__setitem__(slice(None, None, None), [999, 1000])
    EXIT test2
    print(lst)
# Output
#   [999, 1000]
        
      
留下评论