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

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

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

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


当前回答

简单答案:

在类似于python的c++中,当您创建一个对象实例并将其作为参数传递时,不会复制实例本身,因此您可以从函数的外部和内部引用相同的实例,并且可以修改相同对象实例的组件基准,因此外部可以看到更改。

对于基本类型,python和c++的行为也相同,因为现在创建了实例的副本,所以外部看到/修改的实例与函数内部不同。因此,外部看不到内部的变化。

下面是python和c++之间的真正区别:

c++具有地址指针的概念,而c++允许您传递指针,这绕过了对基本类型的复制,因此函数内部可以影响与外部相同的实例,因此外部也可以看到更改。这在python中没有等价的,因此如果没有变通方法(例如创建包装器类型)是不可能的。

这样的指针在python中很有用,但不像在c++中那样有必要,因为在c++中,您只能返回一个实体,而在python中,您可以返回用逗号分隔的多个值(即元组)。因此,在python中,如果您有变量a、b和c,并希望函数持久地修改它们(相对于外部),您可以这样做:

a=4
b=3
c=8

a,b,c=somefunc(a,b,c)
# a,b,c now have different values here

这样的语法在c++中是不容易实现的,因此在c++中您可以这样做:

int a=4
int b=3
int c=8
somefunc(&a,&b,&c)
// a,b,c now have different values here

其他回答

它既不是通过值传递,也不是通过引用传递,而是通过对象调用。参见Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

这里有一句重要的话:

“…变量[名称]不是对象;它们不能由其他变量表示或由对象引用。”

在您的示例中,当调用Change方法时,将为其创建名称空间;var成为该名称空间中字符串对象“Original”的名称。然后,该对象在两个名称空间中都有一个名称。接下来,var='Changed'将var绑定到一个新的字符串对象,因此该方法的命名空间忘记了'Original'。最后,该名称空间被遗忘,字符串“Changed”也随之消失。

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

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'

我解决了类似的要求,如下所示:

要实现更改变量的成员函数,请不要传递变量本身,而是传递包含引用该变量的setattr的functools.partial。在change()内调用functools.partial将执行setttr并更改实际引用的变量。

注意,setattr需要变量的名称作为字符串。

class PassByReference(object):
    def __init__(self):
        self.variable = "Original"
        print(self.variable)        
        self.change(partial(setattr,self,"variable"))
        print(self.variable)

    def change(self, setter):
        setter("Changed")

Python中的“通过引用”与C++/Java中的“引用传递”概念截然不同。

Java&C#:基元类型(包括字符串)通过值传递(副本),引用类型通过引用传递(地址副本),因此调用方可以看到调用函数中参数的所有更改。C++:允许通过引用或通过值传递。如果参数是通过引用传递的,则可以根据参数是否作为常量传递来修改它。但是,无论是否为常量,参数都保持对对象的引用,并且不能将引用指定为指向所调用函数中的其他对象。蟒蛇:Python是“按对象引用传递”,人们常说:“对象引用是按值传递的。”。调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只是在调用者中保存对象的副本。与C++一样,参数可以在函数中修改或不修改-这取决于传递的对象类型。如;不可变对象类型不能在调用的函数中修改,而可变对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个关键区别是,更新的值会在调用的函数中反映出来,而重新初始化的值则不会。将新对象分配给可变变量的作用域是python中函数的本地作用域。@blair conrad提供的例子很好地理解了这一点。

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

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

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

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