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

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

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

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


当前回答

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

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

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

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

其他回答

在这种情况下,方法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,应该很少使用,但实际上有一些变通方法可以有效地将对象当前分配给局部变量,甚至可以从调用函数内部重新分配局部变量。

基本思想是有一个函数可以进行访问,并且可以作为对象传递给其他函数或存储在类中。

一种方法是在包装函数中使用全局(用于全局变量)或非局部(用于函数中的局部变量)。

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

同样的想法适用于读取和删除变量。

对于只读,还有一种更短的方法可以使用lambda:x,它返回一个可调用函数,当被调用时,该函数返回当前值x。这有点像很久以前语言中使用的“按名称调用”。

传递3个包装器来访问变量有点笨拙,因此可以将这些包装器包装到具有代理属性的类中:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

Pythons的“反射”支持使得可以获得能够在给定范围内重新分配名称/变量的对象,而无需在该范围内明确定义函数:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

这里ByRef类包装了字典访问。因此,对wrapped的属性访问被转换为传递的字典中的项访问。通过传递内置局部变量的结果和局部变量的名称,最终访问一个局部变量。3.5版本的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)

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

由于您的示例恰好是面向对象的,因此可以进行以下更改以获得类似的结果:

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

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

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'

关于这一点,已经有很多很好的答案(或者说是观点),我已经读过了,但我想提到一个缺失的答案。常见问题解答部分Python文档中的一个。我不知道发布此页面的日期,但这应该是我们的真实参考:

记住,在Python中,参数是通过赋值传递的。自从赋值只创建对对象的引用,没有别名在调用者和被调用者中的参数名之间,因此没有引用调用本身。

如果您有:

a = SOMETHING

def fn(arg):
    pass

你把它叫做fn(a),你做的和你在作业中做的完全一样。所以会发生这种情况:

arg = a

创建了对SOMETHING的其他引用。变量只是符号/名称/引用。他们不“持有”任何东西。