Python 为什么函数可以修改调用者感知到的某些参数,而不能修改其他参数?

Python 为什么函数可以修改调用者感知到的某些参数,而不能修改其他参数?,python,Python,我试图理解Python处理变量范围的方法。在这个例子中,为什么f能够改变x的值,就像在main中看到的那样,而不是n的值 输出: Before: 1 [0, 1, 2, 3] In f(): 2 [0, 1, 2, 3, 4] After: 1 [0, 1, 2, 3, 4] f实际上并不改变x的值,x始终是对列表实例的相同引用。相反,它会改变此列表的内容 在这两种情况下,引用的副本都会传递给函数。在函数内部 n被分配一个新值。只修改函数内部的引用,而不修改函数外部的引用。 没有为x分配新值

我试图理解Python处理变量范围的方法。在这个例子中,为什么f能够改变x的值,就像在main中看到的那样,而不是n的值

输出:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]
f实际上并不改变x的值,x始终是对列表实例的相同引用。相反,它会改变此列表的内容

在这两种情况下,引用的副本都会传递给函数。在函数内部

n被分配一个新值。只修改函数内部的引用,而不修改函数外部的引用。 没有为x分配新值:函数内部和外部的引用都不会被修改。而是修改x的值。
由于函数内部和外部的x都引用相同的值,因此都可以看到修改。相比之下,在函数内部重新分配n之后,函数内部和外部的n引用了不同的值。

这是因为列表是一个可变对象。您没有将x设置为[0,1,2,3]的值,而是为对象[0,1,2,3]定义了一个标签

您应该这样声明函数f:

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

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


X是一个列表变量,指针的副本被传递给函数,因此X.append4更改列表的内容。但是,您在函数中说过x=[0,1,2,3,4],您不会更改main中x的内容。

我将重命名变量以减少混淆。n->nf或nmain。x->xf或xmain:

当调用函数f时,Python运行库生成一个xmain的副本并将其分配给xf,类似地,还将nmain的副本分配给nf


在n的情况下,复制的值为1

对于x,复制的值不是文字列表[0、1、2、3]。这是对该清单的参考。xf和xmain指向同一个列表,因此当您修改xf时,您也在修改xmain

然而,如果你要写的东西是:

    xf = ["foo", "bar"]
    xf.append(4)
你会发现xmain没有改变。这是因为,在xf=[foo,bar]行中,您必须更改xf以指向一个新列表。对这个新列表所做的任何更改都不会对xmain仍然指向的列表产生影响


希望有帮助-

有些答案在函数调用的上下文中包含单词copy。我觉得很困惑

Python永远不会复制在函数调用期间传递的对象

函数参数是名称。调用函数时,Python会将这些参数绑定到调用方作用域中通过名称传递的任何对象

在Python中,对象可以像列表一样可变,也可以像整数、字符串一样不可变。可以更改的可变对象。不能更改名称,只需将其绑定到另一个对象即可

您的示例不是关于,而是关于Python的

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

下面是一些漂亮的图片。

你已经有了很多答案,我大体上同意J.F.Sebastian的观点,但你可能会发现这是一条有用的捷径:

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

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


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

如果您正确考虑,Python是一种纯粹的传递值语言。python变量在内存中存储对象的位置。Python变量不存储对象本身。将变量传递给函数时,传递的是该变量指向的对象地址的副本

对比这两个函数

def foo(x):
    x[0] = 5

def goo(x):
    x = []
现在,当您在shell中键入

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]
把这个和goo比较一下

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]
在第一种情况下,我们将cow地址的副本传递给foo,foo修改了驻留在那里的对象的状态。对象被修改

在第二种情况下,您将cow地址的副本传递给goo。然后顾继续更改该副本。效果:无

我称之为粉红屋原则。如果你把你的地址复印一份,然后告诉 油漆工把那个地址的房子漆成粉红色,你会得到一栋粉红色的房子。 如果你给画家一份你的地址并告诉他换一个新地址, 你家的地址不变


这个解释消除了许多混乱。Python通过值传递存储的地址变量

Python是按引用值复制的。对象在内存中占据一个字段,引用与该对象关联,但它本身在内存中占据一个字段。名称/值与引用关联。在python函数中
,它总是复制引用的值,因此在代码中,n被复制为一个新名称,当您分配该名称时,它在调用方堆栈中有一个新的空格。但是对于列表,名称也被复制了,但它引用的是同一个memorysence,您从未为列表分配新值。这是python中的一种魔力

如果函数是用完全不同的变量重新编写的,我们调用它们,那么它就很好地说明了这一点。我一开始并没有明白这一点,所以我试着理解/说服自己:

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

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

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

z和x具有相同的id。正如文章所说,对于相同的底层结构,只是不同的标记。

我的一般理解是,任何对象变量,例如列表或dict,都可以通过其函数进行修改。我认为您不能做的是重新分配参数,即在可调用函数中通过引用分配参数

这与许多其他语言是一致的

运行以下简短脚本以了解其工作原理:

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

我已经多次修改了我的答案,并且意识到我不需要说什么,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测试数据: c=idarg arg=123 d=idarg 回来 a=‘测试ID’ b=ida 测试 e=ida b=c=e!=D 此函数不更改原始值 del change_like_mutablearg: arg.append1 arg.insert0,9 arg.remove2 回来 测试_1=[1,2,3] 像可变测试一样进行更改 此函数不可用 def不会像strarg一样改变: arg=[1,2,3] 回来 测试_2=[1,1,1] 你不想改变吗 print不会像可替换的test_2那样更改

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

复制有误导性。Python没有像C这样的变量。Python中的所有名称都是引用。您不能修改名称,只需将其绑定到另一个对象即可。在Python中谈论可变和不可变对象才有意义,而不是它们是名称。@J.F.塞巴斯蒂安:你的说法充其量只是误导。将数字视为引用是没有用的。@dyFunctor:数字是对不可变对象的引用。如果你想换一种方式来思考,你需要解释一些奇怪的特殊情况。如果你认为它们是不可变的,那么就没有特殊情况了。@S.Lott:不管在幕后发生了什么,Guido van Rossum都在设计Python上下了很大的功夫,这样程序员就可以把数字看作是。。。数字。@J.F.,引用已复制。请注意指针短语的副本。这两个位置都获得对对象的引用。n是对不可变对象的引用;x是对可变对象的引用。本文帮助我更好地理解了这个问题,并提出了解决方法和一些高级用途:@Gfy,我以前见过类似的例子,但对我来说,它并没有描述真实世界的情况。如果您正在修改传入的内容,那么给它一个默认值是没有意义的。@MarkRansom,我认为如果您想提供可选的输出目标,如:def foox,l=None:l=l或[];l、 附录**2;返回l[-1]。对于Sebastian代码的最后一行,它表示上述内容对原始列表没有影响。但在我看来,它只对n没有影响,而是改变了主函数中的x。我说的对吗?f中的@user17670:x=[]对主函数中的列表x没有影响。我已经更新了注释,使其更具体。在n的情况下,复制的值…-这是错误的,这里没有复制,除非你计算引用。相反,python使用指向实际对象的“名称”。nf和xf指向nmain和xmain,直到nf=2,其中名称nf更改为指向2。数字是不可变的,列表是可变的。如果你用正确的方式思考,纯指针传递值和引用传递值并没有太大区别……看看goo。如果您纯粹是通过引用传递,它将改变其参数。不,Python不是一种纯粹的按引用传递语言。它通过值传递引用。它与可变性无关。如果您使用x=x+[4]而不是x.append4,那么尽管列表是可变的,但在调用程序中也不会看到任何更改。这与它是否真的发生了变异有关。OTOH,如果你做了x+=[4],那么x就发生了变异,就像x.append4发生的一样,因此调用者会看到变化。这里解释得很好,没有对象变量。在Python中,一切都是一个对象。某些对象公开了mutator方法,即它们是可变的,而其他对象则不公开。Bro末尾的输出丢失。结果如何?这些信息正是我所需要的,谢谢。
def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)
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