Python 为什么函数可以修改调用者感知到的某些参数,而不能修改其他参数?
我试图理解Python处理变量范围的方法。在这个例子中,为什么f能够改变x的值,就像在main中看到的那样,而不是n的值 输出: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分配新值
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