无法理解在 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.fooarg1same 对象。这就是为什么下面一行,

self.foo.age = 23

修改arg1(即Person1)。 因此,变量是可以指向唯一对象的不同“名称”或“标签”(此处为person 对象)。这就解释了为什么baz(Person1)Person1.age 修改为 bar1.foo.age 为27,因为Person1bar1.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中相关的概念。相关概念是“可变对象”和“不可变对象”。列表是可变的,而数字不是,这解释了你观察到的东西。此外,您的 Person1bar1 对象是可变的(这就是您可以更改此人年龄的原因)。您可以在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,而不是更改yx所引用的列表.

上面的三个概念(变量作为名称[用于可变对象]、不对称赋值和可变性/不变性)解释了 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 中传递值和引用的主要内容,如果未能解决你的问题,请参考以下文章

Python中的传值和引用

通过引用传递 - 左值和向量

PHP基础终极版-面试大全 深度理解变量的传值和引用

值和引用

[转]Python中函数的值传递和引用传递

python中函数参数的引用方式