无法理解在 Python 中传递值和引用
Posted
技术标签:
【中文标题】无法理解在 Python 中传递值和引用【英文标题】:Trouble understanding passing values and references in Python 【发布时间】:2011-12-11 21:17:13 【问题描述】:在对象何时更改以及何时不在 Python 中时遇到问题。下面是我设计拙劣的示例:
class person:
age = 21
class bar:
def __init__(self, arg1):
self.foo = arg1
self.foo.age = 23
def baz(arg1):
arg1.age = 27
def teh(arg1):
arg1 = [3,2,1]
Person1 = person()
bar1 = bar(Person1)
print Person1.age
print bar1.foo.age
baz(Person1)
print Person1.age
print bar1.foo.age
meh = [1,2,3]
teh(meh)
print meh
输出是
23
23
27
27
[1, 2, 3]
所以当我们声明 Person1 时,Person1.age 为 21。对该对象的引用被传递给 bar 的另一个类实例 bar1 的类构造函数。对此引用所做的任何更改都会更改 Person1。
当我们将 Person1 传递给普通函数时也是如此,Person1.age 现在等于 27。
但是为什么这不适用于变量“meh”?当然,如果我们分配一个变量a = meh
并更改a = [6, 6, 6]
,那么meh 也会被更改。我糊涂了。有没有关于这一切如何运作的文献?
【问题讨论】:
为了在 *** 中获得正确的代码缩进,您需要使用空格而不是制表符。在您自己的代码中执行此操作也是一个好主意,因为我相信这是大多数人在 Python 中所做的。 【参考方案1】:我可以看到三个基本的 Python 概念可以对这个问题有所启发:
1) 首先,来自可变对象的赋值,例如 in
self.foo = arg1
就像复制一个指针(而不是指向的值):self.foo
和 arg1
是 same 对象。这就是为什么下面一行,
self.foo.age = 23
修改arg1
(即Person1
)。 因此,变量是可以指向唯一对象的不同“名称”或“标签”(此处为person
对象)。这就解释了为什么baz(Person1)
将Person1.age
修改为和 bar1.foo.age
为27,因为Person1
和bar1.foo
只是相同 person
对象的两个名称(Person1 is bar1.foo
在 Python 中返回 True
)。
2) 第二个重要的概念是赋值。在
def teh(arg1):
arg1 = [3,2,1]
变量arg1
是本地的,所以代码
meh = [1,2,3]
teh(meh)
首先是arg1 = meh
,这意味着arg1
是列表meh
的附加(本地)名称;但是,arg1 = [3, 2, 1]
就像是在说“我改变了主意:arg1
从现在开始将成为 new 列表的名称,[3, 2, 1]”。这里要记住的重要一点是,赋值,尽管用“等号”表示,是不对称的:它们为右侧的(可变)对象赋予了额外的名称,在左边给出(这就是为什么你不能做[3, 2, 1] = arg1
,因为左边必须是一个名字[或名字])。所以,arg1 = meh; arg1 = [3, 2, 1]
不能更改 meh
。
3)最后一点和题主有关:“按值传递”和“按引用传递”不是Python中相关的概念。相关概念是“可变对象”和“不可变对象”。列表是可变的,而数字不是,这解释了你观察到的东西。此外,您的 Person1
和 bar1
对象是可变的(这就是您可以更改此人年龄的原因)。您可以在text tutorial 和video tutorial 中找到有关这些概念的更多信息。***也有一些(more technical) information。一个例子说明了可变和不可变之间的行为差异:
x = (4, 2)
y = x # This is equivalent to copying the value (4, 2), because tuples are immutable
y += (1, 2, 3) # This does not change x, because tuples are immutable; this does y = (4, 2) + (1, 2, 3)
x = [4, 2]
y = x # This is equivalent to copying a pointer to the [4, 2] list
y += [1, 2, 3] # This also changes x, because x and y are different names for the same (mutable) object
最后一行不等同于y = y + [1, 2, 3]
,因为这只会将新列表对象放入变量y
,而不是更改y
和x
所引用的列表.
上面的三个概念(变量作为名称[用于可变对象]、不对称赋值和可变性/不变性)解释了 Python 的许多行为。
【讨论】:
“可变对象”与“不可变对象”在这里真的不是很相关;问题是“突变”与“分配”。arg1
可用于改变meh
,但不能用于为meh
分配新值。
@ruakh:问题中的代码确实只是关于赋值和可变性;然而,标题是关于“通过值和引用传递”,所以我认为无论如何提及Python对调用者传递的变量(即可变和不可变对象)的修改(或不修改)的方法是有用的。跨度>
@ruakh:PS:不过,我确实更新了我的帖子,以淡化可变性与不变性方面的重要性。【参考方案2】:
当然,如果我们分配一个变量 a = meh 并改变 a = [6, 6, 6],那么 meh 也会被改变。
不,实际上,它不会:
>>> meh = [1,2,3]
>>> a = meh
>>> a = [6, 6, 6]
>>> print a
[6, 6, 6]
>>> print meh
[1, 2, 3]
这就是覆盖一个变量和修改一个变量指向的实例之间的区别。
列表、字典、集合和对象是可变类型。如果您添加、删除、设置、获取或以其他方式修改它们的实例中,它会更新引用该实例的所有内容。
但是,如果将类型的完全新实例分配给变量,则会更改存储在该变量中的引用,因此旧引用的实例不会更改。
a = [1,2,3] # New instance
a[1] = 4 # Modifying existing instance
b = 'x':1, 'y':2 # New instance
b['x'] = 3 # Modifying existing instance
self.x = [1,2,3] # Modifying existing object instance pointed to by 'self',
# and creating new instance of a list to store in 'self.x'
self.x[0] = 5 # Modifying existing list instance pointed to by 'self.x'
【讨论】:
【参考方案3】:Python 没有按值传递,也没有按引用传递,而是使用按对象传递——换句话说,对象直接传递给函数,并绑定到在函数定义。
当您执行 spam = "green" 时,您已将名称 spam 绑定到字符串对象 "green";如果你然后做鸡蛋=垃圾邮件你没有复制任何东西,你没有制作参考指针;您只是将另一个名称,eggs,绑定到同一个对象(在本例中为“green”)。如果您随后将垃圾邮件绑定到其他内容 (spam = 3.14159),鸡蛋仍将绑定到“绿色”。
在您的 teh
函数中,您不会更改/修改/改变传入的对象,而是将名称 arg1
重新分配给不同的列表。要更改 arg1
,您需要这样:
def teh(arg1):
arg1[:] = [3, 2, 1]
【讨论】:
“按名称传递”(或“按名称调用”)是完全不同的东西,请参阅 cs.sfu.ca/~cameron/Teaching/383/PassByName.html 和 en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name。 Python 的参数传递模型被描述为“共享调用”,而在其他具有相同参数传递的语言中,它被认为是值调用,其值始终是对象的指针/引用。 @delnan:谢谢,已更新为“按对象调用”。 Python 没有公开用于实现按对象调用的底层机制,因此使用术语“按值调用”是有害的。以上是关于无法理解在 Python 中传递值和引用的主要内容,如果未能解决你的问题,请参考以下文章