参数是通过引用传递还是通过值传递?如何通过引用传递,以便下面的代码输出“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]

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

其他回答

我发现其他答案相当长而且复杂,所以我创建了这个简单的图来解释Python处理变量和参数的方式。

或者,你可以使用看起来像这样的ctypes

import ctypes

def f(a):
    a.value=2398 ## resign the value in a function

a = ctypes.c_int(0)
print("pre f", a)
f(a)
print("post f", a)

因为a是一个c整数,而不是python整数,并且通过引用传递。然而,你必须小心,因为可能会发生奇怪的事情,因此不建议

虽然通过引用传递并不能很好地适应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中变量的误解。如果你习惯了大多数传统语言,你会有一个心理模型来描述以下顺序:

a = 1
a = 2

您认为a是存储值1的内存位置,然后更新为存储值2。这不是Python中的工作方式。相反,a开始作为对值为1的对象的引用,然后重新分配为对值为2的对象的参考。这两个对象可能会继续共存,即使a不再指代第一个对象;事实上,它们可以由程序内的任何数量的其他引用共享。

使用参数调用函数时,将创建一个引用传入对象的新引用。这与函数调用中使用的引用不同,因此无法更新该引用并使其引用新对象。在您的示例中:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

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

self.variable是对字符串对象“Original”的引用。当调用Change时,将创建对象的第二个引用变量。在函数内部,您将引用变量重新分配给不同的字符串对象“Changed”,但引用self.variable是独立的,不会更改。

解决此问题的唯一方法是传递一个可变对象。因为两个引用都引用同一个对象,所以对对象的任何更改都会反映在两个位置。

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

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

从技术上讲,Python始终使用引用传递值。我将重复我的另一个回答,以支持我的发言。

Python始终使用引用传递值。没有任何例外。任何变量赋值都意味着复制参考值。没有例外。任何变量都是绑定到引用值的名称。总是

您可以将参考值视为目标对象的地址。地址在使用时自动取消引用。这样,使用引用值时,似乎可以直接使用目标对象。但在两者之间总是有一个参考点,多跳一步就可以到达目标。

下面的示例证明了Python使用的是通过引用传递:

如果参数是按值传递的,则无法修改外部lst。绿色是目标对象(黑色是存储在内存中的值,红色是对象类型),黄色是内存中的参考值,如箭头所示。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部字典。(实际上它也可以画成一个绿色椭圆。颜色和形状只表示它是内部的。)

您可以使用id()内置函数来了解引用值是什么(即目标对象的地址)。

在编译语言中,变量是能够捕获类型值的内存空间。在Python中,变量是绑定到引用变量的名称(内部捕获为字符串),该引用变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分存储目标的引用值。

引用值在Python中隐藏。没有任何用于存储引用值的显式用户类型。但是,您可以使用列表元素(或任何其他合适容器类型的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上不包含在容器中——只有对元素的引用。