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

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

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

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


当前回答

在这种情况下,方法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新手,从昨天开始(尽管我已经编程45年了)。

我来这里是因为我在写一个函数,希望有两个所谓的参数。如果它只是一个out参数,我现在就不会在检查reference/value在Python中的工作方式时感到困惑。我只需要使用函数的返回值。但由于我需要两个这样的参数,我觉得我需要整理一下。

在这篇文章中,我将展示我如何解决我的问题。也许其他来到这里的人会觉得它很有价值,尽管它并不是对主题问题的答案。当然,经验丰富的Python程序员已经知道我使用的解决方案,但这对我来说是新的。

从这里的答案中,我可以很快看出Python在这方面的工作方式有点像Javascript,如果您想要引用功能,则需要使用变通方法。

但后来我在Python中发现了一些我以前在其他语言中没有见过的东西,即可以用简单的逗号分隔方式从函数中返回多个值,如下所示:

def somefunction(p):
    a=p+1
    b=p+2
    c=-p
    return a, b, c

并且你可以在调用端类似地处理它,就像这样

x, y, z = somefunction(w)

这对我来说已经足够好了,我很满意。不需要使用一些变通方法。

在其他语言中,您当然也可以返回许多值,但通常是从对象中返回,您需要相应地调整调用端。

Python的方法很好,也很简单。

如果您想通过引用进行更多模拟,可以执行以下操作:

def somefunction(a, b, c):
    a = a * 2
    b = b + a
    c = a * b * c
    return a, b, c

x = 3
y = 5
z = 10
print(F"Before : {x}, {y}, {z}")

x, y, z = somefunction(x, y, z)

print(F"After  : {x}, {y}, {z}")

这给出了这个结果

Before : 3, 5, 10  
After  : 6, 11, 660  

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

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()

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

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

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

干杯

想想通过赋值而不是通过引用/值传递的东西。这样,只要你明白在正常任务中发生了什么,就会很清楚发生了什么。

因此,当将列表传递给函数/方法时,该列表被分配给参数名称。附加到列表将导致列表被修改。重新分配函数内的列表不会更改原始列表,因为:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

由于不可变类型不能被修改,它们看起来像是通过值传递的——将int传递给函数意味着将int分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。

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

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

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

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

虽然通过引用传递并不能很好地适应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文档建议,更改字典可能不起作用,但对我来说似乎很有用。