参数是通过引用传递还是通过值传递?如何通过引用传递,以便下面的代码输出“Changed”而不是“Original”?

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

另请参阅:为什么函数可以修改调用者感知的某些参数,而不能修改其他参数?


当前回答

我分享了另一种有趣的方式,让人们通过一个方便的工具来理解这个主题——基于@Mark Ransom传递可变列表的示例,可视化巨蟒代码执行

随便玩玩,你就会明白的。

传递字符串

传递列表

其他回答

(编辑-布莱尔更新了他广受欢迎的答案,使其准确无误)

我认为重要的是要注意到,目前获得最多选票的帖子(布莱尔•康拉德),虽然其结果正确,但却具有误导性,并且根据其定义,几乎不正确。虽然有许多语言(如C)允许用户通过引用传递或通过值传递,但Python不是其中之一。

大卫·库尔纳波的回答指向了真正的答案,并解释了为什么布莱尔·康拉德帖子中的行为似乎是正确的,而定义却不正确。

在Python是按值传递的情况下,所有语言都按值传递,因为必须发送一些数据(无论是“值”还是“引用”)。然而,这并不意味着Python是按C程序员会想到的值传递的。

如果你想要这种行为,Blair Conrad的回答很好。但如果你想知道Python既不是通过值传递,也不是通过引用传递的根本原因,请阅读大卫·库尔纳波的答案。

它既不是通过值传递,也不是通过引用传递,而是通过对象调用。参见Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

这里有一句重要的话:

“…变量[名称]不是对象;它们不能由其他变量表示或由对象引用。”

在您的示例中,当调用Change方法时,将为其创建名称空间;var成为该名称空间中字符串对象“Original”的名称。然后,该对象在两个名称空间中都有一个名称。接下来,var='Changed'将var绑定到一个新的字符串对象,因此该方法的命名空间忘记了'Original'。最后,该名称空间被遗忘,字符串“Changed”也随之消失。

Python中的“通过引用”与C++/Java中的“引用传递”概念截然不同。

Java&C#:基元类型(包括字符串)通过值传递(副本),引用类型通过引用传递(地址副本),因此调用方可以看到调用函数中参数的所有更改。C++:允许通过引用或通过值传递。如果参数是通过引用传递的,则可以根据参数是否作为常量传递来修改它。但是,无论是否为常量,参数都保持对对象的引用,并且不能将引用指定为指向所调用函数中的其他对象。蟒蛇:Python是“按对象引用传递”,人们常说:“对象引用是按值传递的。”。调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只是在调用者中保存对象的副本。与C++一样,参数可以在函数中修改或不修改-这取决于传递的对象类型。如;不可变对象类型不能在调用的函数中修改,而可变对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个关键区别是,更新的值会在调用的函数中反映出来,而重新初始化的值则不会。将新对象分配给可变变量的作用域是python中函数的本地作用域。@blair conrad提供的例子很好地理解了这一点。

在这种情况下,方法Change中名为var的变量被分配了对self.variable的引用,并且您立即将字符串分配给var。它不再指向self.variable.下面的代码片段显示了如果您修改了var和self.variaable指向的数据结构(在本例中是一个列表)会发生什么:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

我相信其他人可以进一步澄清这一点。

Python中没有变量

理解参数传递的关键是停止思考“变量”。Python中有名称和对象看起来像变量,但始终区分这三个变量是有用的。

Python有名称和对象。赋值将名称绑定到对象。向函数传递参数还将名称(函数的参数名称)绑定到对象。

这就是它的全部。变异性与这个问题无关。

例子:

a = 1

这将名称a绑定到保存值1的integer类型的对象。

b = x

这将名称b绑定到名称x当前绑定到的同一对象。之后,名称b与名称x不再相关。

请参见Python 3语言参考中的3.1和4.2节。

如何阅读问题中的示例

在问题中显示的代码中,self.Change(self.variable)语句将名称var(在函数Change的作用域中)绑定到保存值“Original”的对象,赋值var='Changed'(在函数Change的主体中)再次将相同的名称分配给某个其他对象(恰好也包含字符串,但可能完全是其他对象)。

如何通过引用传递

因此,如果要更改的对象是可变对象,则没有问题,因为所有内容都是通过引用有效传递的。

如果它是一个不可变对象(例如布尔值、数字、字符串),那么方法是将它包装在一个可变对象中。对于这一点,快速而肮脏的解决方案是一个单元素列表(而不是self.variable,传递[self.variable]并在函数中修改var[0])。更像蟒蛇的方法是引入一个简单的单属性类。该函数接收类的实例并操作属性。