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

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

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

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


当前回答

你在这里得到了一些非常好的答案。

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )

其他回答

在这种情况下,方法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中没有变量

理解参数传递的关键是停止思考“变量”。Python中有名称和对象看起来像变量,但始终区分这三个变量是有用的。

Python有名称和对象。赋值将名称绑定到对象。向函数传递参数还将名称(函数的参数名称)绑定到对象。

这就是它的全部。变异性与这个问题无关。

例子:

a = 1

这将名称a绑定到保存值1的integer类型的对象。

b = x

这将名称b绑定到名称x当前绑定到的同一对象。之后,名称b与名称x不再相关。

请参见Python 3语言参考中的3.1和4.2节。

如何阅读问题中的示例

在问题中显示的代码中,self.Change(self.variable)语句将名称var(在函数Change的作用域中)绑定到保存值“Original”的对象,赋值var='Changed'(在函数Change的主体中)再次将相同的名称分配给某个其他对象(恰好也包含字符串,但可能完全是其他对象)。

如何通过引用传递

因此,如果要更改的对象是可变对象,则没有问题,因为所有内容都是通过引用有效传递的。

如果它是一个不可变对象(例如布尔值、数字、字符串),那么方法是将它包装在一个可变对象中。对于这一点,快速而肮脏的解决方案是一个单元素列表(而不是self.variable,传递[self.variable]并在函数中修改var[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文档建议,更改字典可能不起作用,但对我来说似乎很有用。

简单答案:

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

我使用以下方法将一些Fortran代码快速转换为Python。的确,它不像最初提出的问题那样通过引用传递,但在某些情况下是一个简单的解决方案。

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c