无法理解Python中的传递值和引用

时间:2011-10-20 15:06:39

标签: python oop reference pass-by-reference

在更改对象时以及何时不在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也会被更改。我糊涂了。有没有关于这一切是如何运作的文献?

3 个答案:

答案 0 :(得分:8)

我可以看到三个基本的Python概念可以对这个问题有所启发:

1)首先,来自

中的可变对象的赋值
self.foo = arg1

就像复制指针(而不是指向的值):self.fooarg1相同的对象。这就是为什么接下来的行,

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 tutorialvideo tutorial中找到有关这些概念的更多信息。维基百科也有一些(more technical) information。一个例子说明了mutable和immutable之间的行为差​​异:

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中放置一个新的列表对象,而不是更改{{1}引用的列表}和y

上面的三个概念(变量作为名称[用于可变对象],非对称赋值和可变性/不变性)解释了许多Python的行为。

答案 1 :(得分:6)

  

当然,如果我们分配一个变量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]

覆盖变量与修改变量指向的实例之间的区别。

列表,词典,集合和对象是可变类型。如果您添加,删除,设置,获取或以其他方式修改 in 中的某个实例,它会更新引用该实例的所有内容。

但是,如果您为变量分配了完全新实例,则会更改存储在该变量中的引用,因此不会更改旧引用的实例。


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'

答案 2 :(得分:0)

Python没有pass-by-value,也没有pass-by-reference,而是使用pass-by-object - 换句话说,对象直接传递给函数,并绑定到函数中给出的参数名称功能定义。

当您执行spam =“green”时,您已将垃圾邮件名称绑定到字符串对象“green”;如果你那么做鸡蛋=垃圾邮件,你没有复制任何东西,你没有做出参考指针;你只需将另一个名称egg,绑定到同一个对象(在这种情况下为“green”)。如果你然后将垃圾邮件绑定到其他东西(垃圾邮件= 3.14159),鸡蛋仍将被绑定为“绿色”。

teh函数中,您没有更改/修改/改变传入的对象,而是将名称arg1重新分配给其他列表。要更改arg1,您需要这样:

def teh(arg1):
    arg1[:] = [3, 2, 1]