参数是通过引用传递还是通过值传递?如何通过引用传递,以便下面的代码输出“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中,参数是通过赋值传递的。自从赋值只创建对对象的引用,没有别名在调用者和被调用者中的参数名之间,因此没有引用调用本身。

如果您有:

a = SOMETHING

def fn(arg):
    pass

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

arg = a

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

其他回答

这里的答案有很多见解,但我认为这里没有明确提到另外一点。引用python文档https://docs.python.org/2/faq/programming.html#what-是python中局部和全局变量的规则

“在Python中,仅在函数内部引用的变量是隐式全局的。如果在函数体中的任何位置为变量分配了新值,则假定该变量是局部的。如果变量在函数内部被分配了新的值,则该变量隐式是局部的,您需要将其显式声明为“全局”。虽然一开始有点令人惊讶,但片刻的考虑可以解释这一点。一方面,要求全局分配变量可以防止意外的副作用。另一方面,如果所有全局引用都需要全局引用,那么您将一直使用全局引用。您必须将对内置函数或导入模块组件的每个引用声明为全局引用。这种混乱将破坏全球宣言在确定副作用方面的作用。"

即使在将可变对象传递给函数时,这仍然适用。对我来说,这清楚地解释了分配给对象和在函数中操作对象之间行为差异的原因。

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

给予:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

因此,对未声明为全局的全局变量的赋值将创建一个新的局部对象,并断开与原始对象的链接。

简单答案:

在类似于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

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

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

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

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

我是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  

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

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

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

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