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

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

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

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


当前回答

Python为每个对象分配一个唯一的标识符,可以使用Python的内置id()函数找到该标识符。可以验证函数调用中的实际参数和正式参数是否具有相同的id值,这表明伪参数和实际参数引用了相同的对象。注意,实际参数和对应的伪参数是引用同一对象的两个名称。如果将伪参数重新绑定到函数范围中的新值/对象,这不会影响实际参数仍然指向原始对象的事实,因为实际参数和伪参数是两个名称。以上两个事实可以概括为“参数通过赋值传递”。即。,

dummy_argument = actual_argument

如果将dummy_argument重新绑定到函数体中的新对象,则actual_argument仍然引用原始对象。如果使用dummy_argument[0]=some_thing,那么这也将修改actual_argument[0]。因此,“通过引用传递”的效果可以通过修改传入的对象引用的组件/属性来实现。当然,这需要传递的对象是可变对象。

为了与其他语言进行比较,您可以说Python以与C相同的方式按值传递参数,其中当您“按引用”传递时,实际上是按值传递引用(即指针)

其他回答

很可能不是最可靠的方法,但这是有效的,请记住,您正在重载内置str函数,这通常是您不想做的事情:

import builtins

class sstr(str):
    def __str__(self):
        if hasattr(self, 'changed'):
            return self.changed

        return self

    def change(self, value):
        self.changed = value

builtins.str = sstr

def change_the_value(val):
    val.change('After')

val = str('Before')
print (val)
change_the_value(val)
print (val)

问题来自对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中,参数是通过赋值传递的。自从赋值只创建对对象的引用,没有别名在调用者和被调用者中的参数名之间,因此没有引用调用本身。

如果您有:

a = SOMETHING

def fn(arg):
    pass

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

arg = a

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

Python的传递赋值方案与C++的引用参数选项并不完全相同,但实际上它与C语言(以及其他语言)的参数传递模型非常相似:

不可变的参数实际上是“按值”传递的。整数和字符串等对象是通过对象引用传递的,而不是通过复制传递的,但因为无论如何都不能在原地更改不可变的对象,所以效果很像复制。可变参数是“通过指针”有效传递的字典也通过对象引用传递,这与C的方式类似传递数组作为指针,可变对象可以在函数中的适当位置改变,很像C阵列。

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

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

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

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