我试图理解Python对变量作用域的方法。在这个例子中,为什么f()能够改变x的值,就像main()所感知的那样,但不能改变n的值?

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

输出:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]

请参见:如何通过引用传递变量?


当前回答

Python is copy by value of reference. An object occupies a field in memory, and a reference is associated with that object, but itself occupies a field in memory. And name/value is associated with a reference. In python function, it always copy the value of the reference, so in your code, n is copied to be a new name, when you assign that, it has a new space in caller stack. But for the list, the name also got copied, but it refer to the same memory(since you never assign the list a new value). That is a magic in python!

其他回答

N是一个int(不可变),并且一个副本被传递给函数,因此在函数中您正在更改副本。

X是一个列表(可变),指针的副本被传递给函数,因此x.a pend(4)改变了列表的内容。然而,你在你的函数中说x =[0,1,2,3,4],你不会在main()中改变x的内容。

你已经得到了一些答案,我基本上同意J.F.塞巴斯蒂安的观点,但你可能会发现这是一条有用的捷径:

任何时候看到varname =,都是在函数的作用域内创建了一个新的名称绑定。无论之前varname绑定到什么值,都将在此范围内丢失。

任何时候看到varname.foo()都是在varname上调用一个方法。该方法可以改变varname(例如list.append)。Varname(或者,更确切地说,Varname命名的对象)可能存在于多个作用域中,并且由于它是同一个对象,因此任何更改都将在所有作用域中可见。

[注意,global关键字对第一个情况创建了一个异常]

正如jouell所说。这是一个什么指向什么的问题,我想补充的是,这也是一个=做什么和.append方法做什么之间的区别的问题。

When you define n and x in main, you tell them to point at 2 objects, namely 1 and [1,2,3]. That is what = does : it tells what your variable should point to. When you call the function f(n,x), you tell two new local variables nf and xf to point at the same two objects as n and x. When you use "something"="anything_new", you change what "something" points to. When you use .append, you change the object itself. Somehow, even though you gave them the same names, n in the main() and the n in f() are not the same entity, they only originally point to the same object (same goes for x actually). A change to what one of them points to won't affect the other. However, if you instead make a change to the object itself, that will affect both variables as they both point to this same, now modified, object.

让我们在不定义新函数的情况下说明.append方法和=方法之间的区别:

比较

    m = [1,2,3]
    n = m   # this tells n to point at the same object as m does at the moment
    m = [1,2,3,4] # writing m = m + [4] would also do the same
    print('n = ', n,'m = ',m)

to

    m = [1,2,3]
    n = m
    m.append(4)
    print('n = ', n,'m = ',m)

在第一个代码中,它将打印n = [1,2,3] m =[1,2,3,4],因为在第三行,你没有改变对象[1,2,3],而是你告诉m指向一个新的,不同的对象(使用'='),而n仍然指向原始对象。

在第二段代码中,它将输出n = [1,2,3,4] m =[1,2,3,4]。这是因为在整个代码中m和n仍然指向同一个对象,但是您使用.append方法修改了对象本身(m所指向的对象)…注意,不管你在第三行写m.p append(4)还是n.p append(4),第二段代码的结果都是一样的。

一旦你理解了这一点,剩下的唯一困惑就是真正理解,正如我所说,f()函数中的n和x与main()中的n和x并不相同,它们只是在调用f()时最初指向同一个对象。

请允许我重新编辑。这些概念是我通过尝试错误和internet学习python的经验,主要是stackoverflow。有错误也有帮助。

Python变量使用引用,我认为引用是名称、内存地址和值的关系链接。

当我们执行B = A时,我们实际上创建了A的昵称,现在A有两个名字,A和B,当我们调用B时,我们实际上是在调用A,我们创建了一个墨水到另一个变量的值,而不是创建一个新的相同的值,这就是我们所说的引用。这种想法会导致两个问题。

当我们这样做时

A = [1]
B = A   # Now B is an alias of A

A.append(2)  # Now the value of A had been changes
print(B)
>>> [1, 2]  
# B is still an alias of A
# Which means when we call B, the real name we are calling is A

# When we do something to B,  the real name of our object is A
B.append(3)
print(A)
>>> [1, 2, 3]

这就是我们将参数传递给函数时所发生的情况

def test(B):
    print('My name is B')
    print(f'My value is {B}') 
    print(' I am just a nickname,  My real name is A')
    B.append(2)


A = [1]
test(A) 
print(A)
>>> [1, 2]

我们传递A作为一个函数的参数,但是这个参数在那个函数中的名字是B。 同一个,只是名字不同。 当我们执行b。append时,我们是在执行a。append 当我们向函数传递参数时,我们传递的不是一个变量,而是一个别名。

这里有两个问题。

等号总是创建一个新名称

A = [1]
B = A
B.append(2)
A = A[0]  # Now the A is a brand new name, and has nothing todo with the old A from now on.

B.append(3)
print(A)
>>> 1
# the relation of A and B is removed when we assign the name A to something else
# Now B is a independent variable of hisown.

等号表示一个全新的名字,

这是我最激动的部分

 A = [1, 2, 3]

# No equal sign, we are working on the origial object,
A.append(4)
>>> [1, 2, 3, 4]

# This would create a new A
A = A + [4]  
>>> [1, 2, 3, 4]

这个函数

def test(B):
    B = [1, 2, 3]   # B is a new name now, not an alias of A anymore
    B.append(4)  # so this operation won't effect A
    
A = [1, 2, 3]
test(A)
print(A)
>>> [1, 2, 3]

# ---------------------------

def test(B):
    B.append(4)  # B is a nickname of A, we are doing A
    
A = [1, 2, 3]
test(A)
print(A)
>>> [1, 2, 3, 4]

第一个问题是

方程的左边总是一个全新的名字,新的变量, 除非右边是一个名字,比如B = a,否则只创建别名

第二个问题,有些东西是永远不会改变的,我们不能修改原来的,只能创造一个新的。

这就是我们所说的不可变。

当我们执行A= 123时,我们创建了一个包含名称、值和地址的字典。

当我们执行B = A时,我们将地址和值从A复制到B,所有对B的操作都对A的值产生相同的地址。

当涉及到字符串、数字和元组时。值和地址的组合永远不会改变。当我们将一个str放入某个地址时,它立即被锁定,所有修改的结果将被放入其他地址。

A = 'string'将创建一个受保护的值,并地址存储字符串'string'。目前,还没有内置函数或方法可以用list这样的语法修改字符串。附加,因为此代码修改了地址的原始值。

字符串、数字或元组的值和地址是受保护、锁定、不可变的。

我们对字符串所能做的就是通过a = B.method的语法,我们必须创建一个新名称来存储新的字符串值。

如果你还不明白,请继续讨论。 这个讨论帮助我弄清楚可变/不可变/引用/参数/变量/名称一次,希望这可以做一些帮助的人太。

##############################

我修改了无数次我的答案,意识到我什么都不用说,python已经解释清楚了。

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return
 
test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

这个魔鬼不是引用/值/可变或not /实例,名称空间或变量/列表或str,它是语法,等号。

这是因为列表是一个可变对象。你不是将x设置为[0,1,2,3]的值,你是在为对象[0,1,2,3]定义一个标签。

你应该这样声明你的函数f():

def f(n, x=None):
    if x is None:
        x = []
    ...