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

xkftehaa  于 2021-09-08  发布在  Java
关注(0)|答案(11)|浏览(285)

我试图理解python处理变量范围的方法。在这个例子中,为什么是 f() 能够改变……的价值 x ,如在 main() ,但不是 n ?

  1. def f(n, x):
  2. n = 2
  3. x.append(4)
  4. print('In f():', n, x)
  5. def main():
  6. n = 1
  7. x = [0,1,2,3]
  8. print('Before:', n, x)
  9. f(n, x)
  10. print('After: ', n, x)
  11. main()

输出:

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

jv4diomz1#

如果函数是用完全不同的变量重新编写的,并且我们对它们调用id,那么就很好地说明了这一点。我一开始并没有明白这一点,而是阅读了jfs的文章,并给出了很好的解释,所以我试图理解/说服自己:

  1. def f(y, z):
  2. y = 2
  3. z.append(4)
  4. print ('In f(): ', id(y), id(z))
  5. def main():
  6. n = 1
  7. x = [0,1,2,3]
  8. print ('Before in main:', n, x,id(n),id(x))
  9. f(n, x)
  10. print ('After in main:', n, x,id(n),id(x))
  11. main()
  12. Before in main: 1 [0, 1, 2, 3] 94635800628352 139808499830024
  13. In f(): 94635800628384 139808499830024
  14. After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

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

展开查看全部
ulydmbyx

ulydmbyx2#

这是因为列表是一个可变对象。您没有将x设置为[0,1,2,3]的值,而是为对象[0,1,2,3]定义了一个标签。
您应该像这样声明函数f():

  1. def f(n, x=None):
  2. if x is None:
  3. x = []
  4. ...
rslzwgfq

rslzwgfq3#

n是一个int(不可变),并且一个副本被传递给函数,因此在函数中您正在更改副本。
x是一个列表(可变),指针的副本被传递给函数,因此x.append(4)更改列表的内容。但是,您在函数中说x=[0,1,2,3,4],您不会更改main()中x的内容。

iyfamqjs

iyfamqjs4#

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

e3bfsja2

e3bfsja25#

我的一般理解是,任何对象变量(例如列表或dict等)都可以通过其函数进行修改。我认为您不能做的是重新分配参数,即在可调用函数中通过引用分配参数。
这与许多其他语言是一致的。
运行以下简短脚本以了解其工作原理:

  1. def func1(x, l1):
  2. x = 5
  3. l1.append("nonsense")
  4. y = 10
  5. list1 = ["meaning"]
  6. func1(y, list1)
  7. print(y)
  8. print(list1)
5hcedyr0

5hcedyr06#

如果你想得对,python是一种纯粹的传递值语言。python变量在内存中存储对象的位置。python变量不存储对象本身。将变量传递给函数时,传递的是该变量指向的对象地址的副本。
对比这两个函数

  1. def foo(x):
  2. x[0] = 5
  3. def goo(x):
  4. x = []

现在,当您在shell中键入

  1. >>> cow = [3,4,5]
  2. >>> foo(cow)
  3. >>> cow
  4. [5,4,5]

把这个和goo比较一下。

  1. >>> cow = [3,4,5]
  2. >>> goo(cow)
  3. >>> goo
  4. [3,4,5]

在第一种情况下,我们将cow地址的副本传递给foo,foo修改了驻留在那里的对象的状态。对象被修改。
在第二种情况下,您将cow地址的副本传递给goo。然后顾继续更改该副本。效果:无。
我称之为粉红屋原则。如果你把你的地址复印一份,然后让一个油漆工把这个地址的房子漆成粉红色,你最终会得到一个粉红色的房子。如果你给画家一份你的地址,并告诉他把它改成一个新的地址,你家的地址不会改变。
这个解释消除了许多混乱。python通过值传递存储的地址变量。

展开查看全部
pvcm50d1

pvcm50d17#

我已经多次修改了我的答案,并且意识到我不需要说什么,python已经解释过了。

  1. a = 'string'
  2. a.replace('t', '_')
  3. print(a)
  4. >>> 'string'
  5. a = a.replace('t', '_')
  6. print(a)
  7. >>> 's_ring'
  8. b = 100
  9. b + 1
  10. print(b)
  11. >>> 100
  12. b = b + 1
  13. print(b)
  14. >>> 101
  1. def test_id(arg):
  2. c = id(arg)
  3. arg = 123
  4. d = id(arg)
  5. return
  6. a = 'test ids'
  7. b = id(a)
  8. test_id(a)
  9. e = id(a)
  10. # b = c = e != d
  1. # this function do change original value
  2. del change_like_mutable(arg):
  3. arg.append(1)
  4. arg.insert(0, 9)
  5. arg.remove(2)
  6. return
  7. test_1 = [1, 2, 3]
  8. change_like_mutable(test_1)
  9. # this function doesn't
  10. def wont_change_like_str(arg):
  11. arg = [1, 2, 3]
  12. return
  13. test_2 = [1, 1, 1]
  14. wont_change_like_str(test_2)
  15. print("Doesn't change like a imutable", test_2)

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

展开查看全部
aiqt4smr

aiqt4smr8#

有些答案在函数调用的上下文中包含“copy”一词。我觉得很困惑。
python永远不会复制在函数调用期间传递的对象。
函数参数是名称。调用函数时,python会将这些参数绑定到您传递的任何对象(通过调用方作用域中的名称)。
对象可以是可变的(如列表)或不可变的(如python中的整数、字符串)。可以更改的可变对象。不能更改名称,只需将其绑定到另一个对象即可。
您的示例并不是关于作用域或名称空间,而是关于python中对象的命名、绑定和可变性。

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

下面是关于其他语言中的变量和python中的名称之间差异的漂亮图片。

gudnpqoy

gudnpqoy9#

你已经有了很多答案,我大体上同意j.f.塞巴斯蒂安的观点,但你可能会发现这是一条有用的捷径:
你看到什么时候 varname = ,您正在函数范围内创建新的名称绑定。不管什么价值 varname 在此范围内丢失之前绑定到的。
你看到什么时候 varname.foo() 您正在调用上的方法 varname . 该方法可能会改变varname(例如。 list.append ). varname (或者,更确切地说,是指 varname 名称)可能存在于多个作用域中,并且由于它是同一个对象,因此任何更改都将在所有作用域中可见。
[请注意 global 关键字创建第一个案例的异常]

oiopk7p5

oiopk7p510#

f 实际上不会改变 x (始终是对列表示例的相同引用)。相反,它会改变此列表的内容。
在这两种情况下,引用的副本都会传递给函数。在函数内部, n 获取指定的新值。只修改函数内部的引用,而不修改函数外部的引用。 x 不会被分配新值:函数内外的引用都不会被修改。相反 x 的值被修改。
既然 x 函数内部和外部引用相同的值,都可以看到修改。相比之下 n 函数的内部和外部指的是之后的不同值 n 已在函数内部重新分配。

l7mqbcuq

l7mqbcuq11#

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

  1. def f(nf, xf):
  2. nf = 2
  3. xf.append(4)
  4. print 'In f():', nf, xf
  5. def main():
  6. nmain = 1
  7. xmain = [0,1,2,3]
  8. print 'Before:', nmain, xmain
  9. f(nmain, xmain)
  10. print 'After: ', nmain, xmain
  11. main()

当调用函数f时,python运行库生成一个xmain的副本并将其分配给xf,类似地,还将nmain的副本分配给nf。
在n的情况下,复制的值为1。
对于x,复制的值不是文字列表[0、1、2、3]。这是对该清单的参考。xf和xmain指向同一个列表,因此当您修改xf时,您也在修改xmain。
然而,如果你要写的东西是:

  1. xf = ["foo", "bar"]
  2. xf.append(4)

你会发现xmain没有改变。这是因为,在xf=[“foo”,“bar”]行中,您必须更改xf以指向一个新列表。对这个新列表所做的任何更改都不会对xmain仍然指向的列表产生影响。
希望有帮助。:-)

展开查看全部

相关问题