在理解Python中传递值和引用时遇到问题

在理解Python中传递值和引用时遇到问题,python,oop,reference,pass-by-reference,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

对象何时更改以及何时不在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。对该对象的引用传递给另一个名为bar1的bar类实例的类构造函数。对本参考文件所做的任何更改都将改变人员1

当我们把Person1传递给一个普通函数时也是这样,Person1.age现在等于27


但是为什么这个对变量“meh”不起作用呢?当然,如果我们分配一个变量
a=meh
并改变
a=[6,6,6]
,那么meh也会改变。我很困惑。有关于这一切是如何工作的文献吗?

我可以看到三个基本的Python概念,它们可以为这个问题提供一些线索:

1) 首先,来自可变对象的赋值,如

self.foo = arg1
类似于复制指针(而不是指向的值):
self.foo
arg1
是同一个对象。这就是为什么接下来的路线

self.foo.age = 23
修改
arg1
(即
Person1
因此变量是不同的“名称”或“标签”,可以指向唯一的对象(这里是
对象)。这解释了为什么
baz(Person1)
Person1.age
bar1.foo.age
修改为27,因为
Person1
bar1.foo
只是同一
person
对象的两个名称(
Person1是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
从现在起将成为一个新列表的名称,[3,2,1]”。这里需要记住的重要一点是,赋值,尽管用“等号”表示,但是不对称的:它们在右边给一个(可变)对象一个附加的名称,在左边给它一个附加的名称(这就是为什么你不能做
[3,2,1]=arg1
,因为左边必须是一个名称[或多个名称]). 因此,
arg1=meh;arg1=[3,2,1]
无法更改
meh

3) 最后一点与问题标题有关:“通过值传递”和“通过引用传递”在Python中不是相关的概念。相关概念为“可变对象”和“不可变对象”。列表是可变的,而数字是不可变的,这就解释了你观察到的情况。此外,您的
Person1
bar1
对象是可变的(这就是您可以更改此人年龄的原因)。您可以在a和a中找到有关这些概念的更多信息。维基百科也有一些。一个示例说明了可变和不可变行为之间的差异:

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的许多行为

当然,如果我们分配一个变量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]
这是覆盖变量和修改变量指向的实例之间的区别

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

但是,如果将该类型的全新实例分配给变量,则会更改存储在该变量中的引用,因此不会更改旧引用实例



Python没有按值传递,也没有按引用传递,而是使用按对象传递——换句话说,对象直接传递到函数中,并绑定到函数定义中给定的参数名

当您执行spam=“green”时,您已将名称spam绑定到字符串对象“green”;如果你没有复制任何东西,你就没有做参考指针;您只需将另一个名称eggs绑定到同一个对象(本例中为“绿色”)。如果将垃圾邮件绑定到其他内容(spam=3.14159),则鸡蛋仍将绑定到“绿色”

teh
函数中,您没有更改/修改/变异传入的对象,而是将名称
arg1
重新指定给不同的列表。要更改arg1,请执行以下操作:

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

为了在StackOverflow中获得正确的代码缩进,需要使用空格而不是表格。在自己的代码中这样做也是一个好主意,因为我相信大多数人在Python中都是这样做的。“按名称传递”(或“按名称调用”)是完全不同的事情,请参见和。Python的参数传递模型被描述为“通过共享调用”,在具有相同参数传递的其他语言中,它被视为通过值调用,值始终是指向对象的指针/引用。@delnan:谢谢,更新为“通过对象调用”。Python没有公开用于实现对象调用的底层机制,因此使用术语“值调用”是有害的;问题是“变异”与“分配”
arg1
可用于变异
meh
,但不能用于为
meh
@ruakh分配新值:问题中的代码确实只是关于赋值和可变性;然而,标题是关于“路过瓦卢”
def teh(arg1):
    arg1[:] = [3, 2, 1]