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

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

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

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


当前回答

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

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

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

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

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

其他回答

下面是Python中使用的pass by对象概念的简单解释(我希望)。每当您将对象传递给函数时,对象本身就会被传递(Python中的对象实际上是其他编程语言中的值),而不是对该对象的引用。换句话说,当您拨打电话时:

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

正在传递实际对象-[0,1](在其他编程语言中称为值)。因此,实际上函数change_me将尝试执行以下操作:

[0, 1] = [1, 2, 3]

这显然不会改变传递给函数的对象。如果函数看起来像这样:

def change_me(list):
   list.append(2)

那么该调用将导致:

[0, 1].append(2)

这显然会改变对象。这个答案很好地解释了这一点。

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])。更像蟒蛇的方法是引入一个简单的单属性类。该函数接收类的实例并操作属性。

正如你所说的,你需要一个可变的对象,但我建议你检查全局变量,因为它们可以帮助你甚至解决这类问题!

http://docs.python.org/3/faq/programming.html#what-是python中局部和全局变量的规则

例子:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2

数据类呢?此外,它允许您应用类型限制(也称为“类型提示”)。

from dataclasses import dataclass

@dataclass
class Holder:
    obj: your_type # Need any type? Use "obj: object" then.

def foo(ref: Holder):
    ref.obj = do_something()

我同意人们的看法,在大多数情况下,你最好考虑不要使用它。

然而,当我们谈论上下文时,我们有必要知道这一点。

不过,您可以设计显式上下文类。在进行原型设计时,我更喜欢数据类,因为来回序列化它们很容易。

干杯

我通常使用的一个简单技巧是将其包装在列表中:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(是的,我知道这可能很不方便,但有时这样做很简单。)