How do I pass a variable by reference?
python文档似乎不清楚参数是通过引用还是通过值传递的,下面的代码生成的值"original"不变。
1 2 3 4 5 6 7 8 | class PassByReference: def __init__(self): self.variable = 'Original' self.change(self.variable) print(self.variable) def change(self, var): var = 'Changed' |
我可以通过实际引用传递变量吗?
参数通过赋值传递。这背后的理由是双重的:
所以:
如果你将一个可变的对象传递给一个方法,这个方法会得到一个对同一个对象的引用,你可以让它发生变化,让你的心高兴,但是如果你在方法中重新绑定引用,外部的作用域将对它一无所知,完成后,外部的引用仍然会指向原始的对象。
如果将不可变的对象传递给方法,则仍然无法重新绑定外部引用,甚至无法改变对象。
为了更清楚地说明这一点,我们来举几个例子。
列表-可变类型让我们尝试修改传递给方法的列表:
1 2 3 4 5 6 7 8 9 10 | def try_to_change_list_contents(the_list): print('got', the_list) the_list.append('four') print('changed to', the_list) outer_list = ['one', 'two', 'three'] print('before, outer_list =', outer_list) try_to_change_list_contents(outer_list) print('after, outer_list =', outer_list) |
输出:
1 2 3 4 | before, outer_list = ['one', 'two', 'three'] got ['one', 'two', 'three'] changed to ['one', 'two', 'three', 'four'] after, outer_list = ['one', 'two', 'three', 'four'] |
由于传入的参数是对
现在让我们看看当我们试图更改作为参数传入的引用时会发生什么:
1 2 3 4 5 6 7 8 9 10 | def try_to_change_list_reference(the_list): print('got', the_list) the_list = ['and', 'we', 'can', 'not', 'lie'] print('set to', the_list) outer_list = ['we', 'like', 'proper', 'English'] print('before, outer_list =', outer_list) try_to_change_list_reference(outer_list) print('after, outer_list =', outer_list) |
输出:
1 2 3 4 | before, outer_list = ['we', 'like', 'proper', 'English'] got ['we', 'like', 'proper', 'English'] set to ['and', 'we', 'can', 'not', 'lie'] after, outer_list = ['we', 'like', 'proper', 'English'] |
由于
它是不可变的,所以我们无法更改字符串的内容。
现在,让我们尝试更改引用
1 2 3 4 5 6 7 8 9 10 | def try_to_change_string_reference(the_string): print('got', the_string) the_string = 'In a kingdom by the sea' print('set to', the_string) outer_string = 'It was many and many a year ago' print('before, outer_string =', outer_string) try_to_change_string_reference(outer_string) print('after, outer_string =', outer_string) |
输出:
1 2 3 4 | before, outer_string = It was many and many a year ago got It was many and many a year ago set to In a kingdom by the sea after, outer_string = It was many and many a year ago |
同样,由于
我希望这能把事情弄清楚一点。
edit:注意到这并不能回答@david最初提出的问题,"我是否可以通过实际引用传递变量?"我们来研究一下。
我们怎么解决这个问题?正如@andera的答案所示,您可以返回新值。这不会改变传递信息的方式,但会让您获得想要返回的信息:
1 2 3 4 5 6 | def return_a_whole_new_string(the_string): new_string = something_to_do_with_the_old_string(the_string) return new_string # then you could call it like my_string = return_a_whole_new_string(my_string) |
如果您真的想避免使用返回值,可以创建一个类来保存您的值并将其传递到函数中,或者使用现有的类,如列表:
1 2 3 4 5 6 7 8 9 | def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change): new_string = something_to_do_with_the_old_string(stuff_to_change[0]) stuff_to_change[0] = new_string # then you could call it like wrapper = [my_string] use_a_wrapper_to_simulate_pass_by_reference(wrapper) do_something_with(wrapper[0]) |
虽然这看起来有点麻烦。
问题来自对Python中的变量的误解。如果你习惯了大多数的传统语言,你就有了一个心理模型,可以按照以下顺序进行:
1 2 | a = 1 a = 2 |
您认为
当使用参数调用函数时,将创建一个引用传入对象的新引用。这与函数调用中使用的引用不同,因此无法更新该引用并使其引用新对象。在您的示例中:
1 2 3 4 5 6 | def __init__(self): self.variable = 'Original' self.Change(self.variable) def Change(self, var): var = 'Changed' |
唯一的方法是传递一个可变的对象。因为这两个引用引用同一个对象,所以对该对象的任何更改都会反映在这两个地方。
1 2 3 4 5 6 | def __init__(self): self.variable = ['Original'] self.Change(self.variable) def Change(self, var): var[0] = 'Changed' |
我发现其他的答案相当长和复杂,所以我创建了这个简单的图来解释Python处理变量和参数的方式。
它既不是传递值也不是传递引用-它是由对象调用的。见Fredrik Lundh:
http://effbot.org/zone/call-by-object.htm(按对象调用)
这是一个重要的引述:
"...variables [names] are not objects; they cannot be denoted by other variables or referred to by objects."
在您的示例中,当调用
想想那些通过赋值而不是通过引用/值传递的东西。这样,只要你理解在正常作业中发生的事情,一切都很清楚。
因此,当将列表传递给函数/方法时,该列表被分配给参数名。附加到列表将导致列表被修改。在函数内重新分配列表不会更改原始列表,因为:
1 2 3 4 5 | a = [1, 2, 3] b = a b.append(4) b = ['a', 'b'] print a, b # prints [1, 2, 3, 4] ['a', 'b'] |
由于不能修改不可变类型,它们看起来像是按值传递的——将int传递给函数意味着将int分配给函数参数。您只能重新分配它,但它不会改变原始变量的值。
effbot(又名fredrik lundh)将python的变量传递样式描述为按对象调用:http://effbot.org/zone/call-by-object.htm
对象在堆上分配,指向它们的指针可以在任何地方传递。
当进行诸如
x = 1000 这样的赋值时,将创建一个字典条目,该条目将当前命名空间中的字符串"x"映射到指向包含1000的整数对象的指针。使用
x = 2000 更新"x"时,将创建一个新的整数对象,并更新字典以指向新对象。旧的一千个对象是不变的(可能还活着,也可能不活着,这取决于是否有其他的对象)。当您执行新的赋值(如
y = x 时,将创建一个新的字典条目"y",该条目指向与"x"条目相同的对象。像字符串和整数这样的对象是不可变的。这仅仅意味着在创建对象之后,没有方法可以更改它。例如,一旦创建了整数对象1000,它将永远不会改变。数学是通过创建新的整数对象来完成的。
像列表这样的对象是可变的。这意味着可以通过指向对象的任何内容来更改对象的内容。例如,
x = []; y = x; x.append(10); print y 将打印[10] 。已创建空列表。"x"和"y"都指向同一列表。append方法改变(更新)列表对象(如向数据库添加记录),结果对"x"和"y"都可见(就像数据库更新对该数据库的每个连接都可见一样)。
希望你能澄清这个问题。
从技术上讲,python总是使用pass-by引用值。我要重复我的另一个答案来支持我的陈述。
python总是使用传递引用值。没有例外。任何变量赋值都意味着复制引用值。也不例外。任何变量都是绑定到引用值的名称。总是。
可以将引用值视为目标对象的地址。地址在使用时自动取消引用。这样,使用引用值时,您似乎直接使用目标对象。但是在两者之间总是有一个参照物,要跳到目标上还要多走一步。
下面的示例证明了Python使用了按引用传递:
如果参数是按值传递的,则不能修改外部
您可以使用
在编译语言中,变量是能够捕获类型值的内存空间。在Python中,变量是绑定到保存目标对象引用值的引用变量的名称(在内部捕获为字符串)。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。
引用值隐藏在python中。没有任何用于存储引用值的显式用户类型。但是,可以使用一个列表元素(或任何其他合适容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上不包含在容器中——只有对元素的引用才是。
我通常使用的一个简单技巧是将它包装在一个列表中:
1 2 3 4 5 6 | def Change(self, var): var[0] = 'Changed' variable = ['Original'] self.Change(variable) print variable[0] |
(是的,我知道这很不方便,但有时做起来很简单。)
(编辑-布莱尔更新了他广受欢迎的答案,使其准确无误)
我认为重要的是要注意到,目前获得最多选票的职位(布莱尔·康拉德),虽然其结果是正确的,但具有误导性,并且根据其定义是不正确的。虽然有许多语言(如C)允许用户通过引用或传递值,但python不是其中之一。
大卫·库尔纳波的回答指向了真实的答案,并解释了为什么布莱尔·康拉德的文章中的行为似乎是正确的,而定义却不正确。
如果python是传递值,那么所有语言都是传递值,因为必须发送一些数据(无论是"值"还是"引用")。然而,这并不意味着在C程序员会想到的意义上,python是传递值。
如果你想这样做,布莱尔·康拉德的回答很好。但是如果你想知道为什么python既不是传递值,也不是传递引用,那么请阅读大卫·库纳波的答案。
python中没有变量
理解参数传递的关键是停止思考"变量"。在python中有名称和对象,它们在一起看起来像变量,但总是区分这三个变量是有用的。
这就是一切。易变性与这个问题无关。
例子:
1 | a = 1 |
这会将名称
1 | b = x |
这会将名称
请参见Python3语言参考中的第3.1和4.2节。
因此,在问题中所示的代码中,语句
你得到了一些很好的答案。
1 2 3 4 5 6 7 8 9 10 11 12 | 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' ) |
在这种情况下,方法
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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的PASS分配方案与C++的参考参数选项不完全相同,但实际上与C语言(和其他语言)的参数传递模型非常类似:
- 不可变参数实际上是"按值"传递的。像整数和字符串这样的对象是由对象引用传递的,而不是通过复制传递的,但是由于无论如何都不能在适当的位置更改不可变对象,因此效果很像复制。
- 可变参数通过"指针"有效传递。对象如列表字典也通过对象引用传递,这类似于C将数组作为指针传递。可以在函数中就地更改可变对象,很像C数组。
正如您所说,您需要有一个可变的对象,但让我建议您检查全局变量,因为它们可以帮助您甚至解决此类问题!
http://docs.python.org/3/faq/programming.html python中局部变量和全局变量的规则是什么?
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | >>> def x(y): ... global z ... z = y ... >>> x <function x at 0x00000000020E1730> >>> y Traceback (most recent call last): File"<stdin>", line 1, in <module> NameError: name 'y' is not defined >>> z Traceback (most recent call last): File"<stdin>", line 1, in <module> NameError: name 'z' is not defined >>> x(2) >>> x <function x at 0x00000000020E1730> >>> y Traceback (most recent call last): File"<stdin>", line 1, in <module> NameError: name 'y' is not defined >>> z 2 |
这里有很多关于答案的见解,但我认为这里没有明确提到另外一点。从python文档中引用https://docs.python.org/2/faq/programming.html python中局部和全局变量的规则是什么
"在python中,只在函数内部引用的变量是隐式全局的。如果在函数体中的任何位置为变量分配了一个新值,则假定该变量为局部变量。如果在函数中为变量分配了一个新值,则该变量是隐式局部变量,您需要将其显式声明为"global"。虽然一开始有点惊讶,但片刻的考虑可以解释这一点。一方面,为指定的变量要求全局性可以提供一个防止意外副作用的条。另一方面,如果所有全局引用都需要全局引用,那么您将一直使用全局引用。您必须将对内置函数或导入模块的组件的每个引用声明为全局引用。这种混乱将破坏全球声明识别副作用的有效性。"
即使将可变对象传递给函数,这仍然适用。对我来说,清楚地解释了分配给对象和在函数中操作对象之间行为差异的原因。
1 2 3 4 5 6 7 8 9 | 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) |
给予:
1 2 3 4 | Original [1, 2, 3] 4454645632 Received [1, 2, 3] 4454645632 Changed to [0, 0, 0] 4474591928 After [1, 2, 3] 4454645632 |
因此,对未声明为全局的全局变量的赋值将创建一个新的本地对象,并断开与原始对象的链接。
下面是对Python中使用的
1 2 3 4 5 | def change_me(list): list = [1, 2, 3] my_list = [0, 1] change_me(my_list) |
正在传递实际对象-[0,1](在其他编程语言中称为值)。因此,实际上,函数
1 | [0, 1] = [1, 2, 3] |
这显然不会改变传递给函数的对象。如果函数如下所示:
1 2 | def change_me(list): list.append(2) |
然后调用将导致:
1 | [0, 1].append(2) |
这显然会改变物体。这个答案解释得很好。
除了所有关于这些东西如何在Python中工作的很好的解释之外,我没有看到一个简单的关于这个问题的建议。就像创建对象和实例一样,处理实例变量和更改实例变量的方法如下:
1 2 3 4 5 6 7 8 | class PassByReference: def __init__(self): self.variable = 'Original' self.Change() print self.variable def Change(self): self.variable = 'Changed' |
在实例方法中,通常使用
另一种解决方案是创建这样的静态方法:
1 2 3 4 5 6 7 8 9 10 | class PassByReference: def __init__(self): self.variable = 'Original' self.variable = PassByReference.Change(self.variable) print self.variable @staticmethod def Change(var): var = 'Changed' return var |
有一个小技巧可以通过引用传递一个对象,即使语言不能使之成为可能。它也在Java中工作,它是带有一个项目的列表。;-)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class PassByReference: def __init__(self, name): self.name = name def changeRef(ref): ref[0] = PassByReference('Michael') obj = PassByReference('Peter') print obj.name p = [obj] # A pointer to obj! ;-) changeRef(p) print p[0].name # p->name |
这是一个丑陋的黑客,但它起作用。-P
我使用以下方法快速地将两个Fortran代码转换为python。是的,当最初的问题被提出时,它不是通过引用传递的,但在某些情况下是一个简单的工作。
1 2 3 4 5 6 7 8 9 10 11 | 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 |
虽然pass-by引用不适合python,而且很少使用,但有一些解决方法可以实际工作,以使对象当前分配给局部变量,甚至从被调用函数内部重新分配局部变量。
其基本思想是拥有一个可以进行访问的函数,并且可以作为对象传递到其他函数或存储在类中。
一种方法是在包装函数中使用
1 2 3 4 5 6 7 8 | def change(wrapper): wrapper(7) x = 5 def setter(val): global x x = val print(x) |
同样的思想也适用于读取和使用一个变量。
对于只是阅读,甚至还有一种更短的方法来使用
传递3个包装器来访问变量有点难,因此可以将这些包装器包装到具有代理属性的类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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的"反射"支持使获得一个对象成为可能,该对象能够在给定范围内重新分配名称/变量,而无需在该范围内显式定义函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) |
在这里,
考虑到python处理值和引用这些值的方式,引用任意实例属性的唯一方法是按名称:
1 2 3 4 5 6 7 8 | class PassByReferenceIsh: def __init__(self): self.variable = 'Original' self.change('variable') print self.variable def change(self, var): self.__dict__[var] = 'Changed' |
当然,在真正的代码中,您可以在dict查找中添加错误检查。
Python中的PASS引用与C++/Java中的按引用传递概念有很大的不同。
- Java:通过引用传递所有内容,因此调用函数中的参数所做的所有更改都是调用方可见的。
- C++:允许通过引用或通过值传递。如果参数是通过引用传递的,则可以根据参数是否作为常量传递来修改它。但是,无论常量与否,参数维护对对象的引用,并且不能将引用分配给所调用函数中的其他对象。
- Python:python是"pass-by-object-reference",其中常有这样的说法:"object-references是按值传递的。"[此处阅读]1。调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只在调用者中保存对象的副本。像C++一样,一个参数可以在函数中被修改或不被修改,这取决于传递的对象的类型。在被调用函数中不能修改不可变的对象类型,而可变的对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个重要区别是,更新后的值会反映回被调用的函数中,而重新初始化的值则不会。将新对象分配给可变变量的范围是Python中函数的局部范围。@blair conrad提供的例子很好地理解了这一点。
由于您的示例恰好是面向对象的,因此您可以进行以下更改以获得类似的结果:
1 2 3 4 5 6 7 8 9 10 11 12 | 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' |
您只能使用空类作为实例来存储引用对象,因为内部对象属性存储在实例字典中。请参见示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class RefsObj(object): "A class which helps to create references to variables." pass ... # an example of usage def change_ref_var(ref_obj): ref_obj.val = 24 ref_obj = RefsObj() ref_obj.val = 1 print(ref_obj.val) # or print ref_obj.val for python2 change_ref_var(ref_obj) print(ref_obj.val) |