如何通过引用传递variables?
Python文档似乎不清楚参数是通过引用还是值传递的,并且以下代码会生成未更改的值“原始”
class PassByReference: def __init__(self): self.variable = 'Original' self.change(self.variable) print(self.variable) def change(self, var): var = 'Changed'
有什么我可以做的,通过实际参考传递variables?
参数通过赋值传递 。 这背后的理由是双重的:
- 传入的参数实际上是一个对象的引用(但引用是通过值传递的)
- 一些数据types是可变的,而另一些则不是
所以:
-
如果将一个可变对象传递给一个方法,该方法获得对该同一对象的引用,并且可以将其改变为您心中的喜悦,但是如果您在该方法中重新引用该引用,那么外部作用域将不会知道该对象,你完成了,外部引用仍然会指向原始对象。
-
如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至不能对该对象进行变异。
为了更清楚一点,让我们举一些例子。
列表 – 一个可变的types
让我们尝试修改传递给方法的列表:
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)
输出:
before, outer_list = ['one', 'two', 'three'] got ['one', 'two', 'three'] changed to ['one', 'two', 'three', 'four'] after, outer_list = ['one', 'two', 'three', 'four']
由于传入的参数是对outer_list
的引用,而不是其副本的副本,所以我们可以使用变异列表方法对其进行更改,并将变化反映在外部作用域中。
现在让我们看看当我们试图改变传入的参数作为参数时会发生什么:
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)
输出:
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']
由于the_list
参数是按值传递的,因此the_list
分配一个新列表并不会影响方法外部的代码。 the_list
是outer_list
引用的副本,我们将the_list
指向新列表,但无法更改outer_list
指向的位置。
string – 一个不可变的types
它是不可变的,所以我们没有办法改变string的内容
现在,我们尝试改变参考
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)
输出:
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
同样,由于the_string
参数是按值传递的,因此将一个新的stringthe_string
它并不会影响方法外部的代码。 the_string
是outer_string
引用的副本,我们把the_string
指向了一个新的string,但是没有办法改变outer_string
指向的位置。
我希望这个清理一点点。
编辑:有人指出,这并没有回答@David最初的问题,“有什么我可以做的,通过实际参考传递variables?”。 让我们来研究一下。
我们如何解决这个问题?
正如@Andrea的答案所示,您可以返回新的值。 这不会改变事物的传递方式,但却可以让你获得想要的信息:
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)
如果你真的想避免使用返回值,你可以创build一个类来保存你的值,并将其传递给函数或使用现有的类,如列表:
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中的variables。 如果您习惯了大多数传统语言,那么您可以通过以下顺序进行思考:
a = 1 a = 2
您认为a
是存储值1
的内存位置,然后更新为存储值2
。 这不是Python中的事情。 相反, a
开始作为对值为1
的对象的引用,然后被重新分配为具有值2
的对象的引用。 这两个对象可能会继续共存,即使a
对象不再是第一个对象, 实际上它们可以被程序内的任何其他引用共享。
使用参数调用函数时,会创build一个引用传入对象的新引用。它与函数调用中使用的引用分开,因此无法更新该引用并使其引用新的对象。 在你的例子中:
def __init__(self): self.variable = 'Original' self.Change(self.variable) def Change(self, var): var = 'Changed'
self.variable
是对string对象'Original'
的引用。 当您调用Change
您将创build第二个参考var
到对象。 在函数内部,将引用var
重新分配给不同的string对象'Changed'
,但引用self.variable
是分开的,不会更改。
唯一的方法是传递一个可变的对象。 因为两个引用都指向同一个对象,所以对对象的任何更改都会反映在两个地方。
def __init__(self): self.variable = ['Original'] self.Change(self.variable) def Change(self, var): var[0] = 'Changed'
它不是传值或传递引用,而是逐个对象。 Fredrik Lundh看到这个:
http://effbot.org/zone/call-by-object.htm
这是一个重要的报价:
“…variables[名称] 不是对象;它们不能被其他variables表示或被对象引用”。
在你的例子中,当调用Change
方法时 – 为它创build一个命名空间 ; var
在string对象'Original'
成为该名称空间内的名称。 那个对象在两个名字空间中有一个名字。 接下来, var = 'Changed'
将var
绑定到一个新的string对象,因此该方法的名字空间忘记了'Original'
。 最后,这个名字空间被遗忘了,string'Changed'
随之被'Changed'
。
我发现其他的答案很长很复杂,所以我创build了这个简单的图来解释Python对variables和参数的处理方式。
想想通过分配而不是通过参考/按价值传递的东西。 那样的话,总而言之,只要你明白在正常任务中会发生什么事情就会发生什么。
所以,当将一个列表传递给一个函数/方法时,该列表被分配给参数名称。 追加到列表将导致列表被修改。 在函数内重新分配列表不会改变原始列表,因为:
a = [1, 2, 3] b = a b.append(4) b = ['a', 'b'] print a, b # prints [1, 2, 3, 4] ['a', 'b']
由于不可变types不能被修改,它们看起来像被传递的值 – 传递一个int到一个函数意味着分配int的函数参数。 您只能重新分配,但不会更改原始variables值。
从技术上讲, Python总是使用传递参考值 。 我要重复我的另一个答复来支持我的发言。
Python总是使用传递引用值。 没有任何例外。 任何可变分配意味着复制参考值。 没有例外。 任何variables都是绑定到参考值的名字。 总是。
您可以将参考值视为目标对象的地址。 地址在使用时自动解除引用。 这样,使用参考值,似乎你直接与目标对象工作。 但是在这之间总是有一个参考,还有一步要跳到目标。
下面是certificatePython使用引用传递的例子:
如果参数是按值传递的,则不能修改外层。 绿色是目标对象(黑色是存储在内部的值,红色是对象types),黄色是内部具有参考值的内存 – 如箭头所示。 蓝色实心箭头是传递给函数的参考值(通过虚线的蓝色箭头path)。 丑陋的暗黄色是内部字典。 (它实际上也可以画成一个绿色的椭圆,颜色和形状只是说它是内部的。)
您可以使用id()
内置函数来了解引用值是什么(即目标对象的地址)。
在编译语言中,variables是一个能够捕获types值的内存空间。 在Python中,variables是绑定到引用variables的名称(在内部捕获为string),引用variables将引用值保存到目标对象。 variables的名称是内部字典中的键,该字典项的值部分将引用值存储到目标。
参考值在Python中隐藏。 没有任何明确的用户types来存储参考值。 但是,可以使用list元素(或任何其他合适的容器types中的元素)作为引用variables,因为所有容器都将元素也作为对目标对象的引用进行存储。 换句话说,元素实际上并不包含在容器内 – 只有对元素的引用。
Effbot(又名Fredrik Lundh)将Python的variables传递风格描述为逐个对象: http : //effbot.org/zone/call-by-object.htm
对象分配在堆上,指向它们的指针可以在任何地方传递。
-
当您进行
x = 1000
等赋值时,会创build一个字典条目,将当前名称空间中的string“x”映射到指向包含一千个整数对象的指针。 -
当用
x = 2000
更新“x”时,会创build一个新的整数对象,并更新字典以指向新的对象。 旧的一千个对象是不变的(根据是否有其他的东西指向这个对象,它可能也可能不是活着的)。 -
当您执行新的赋值(如
y = x
,会创build一个新的字典条目“y”,指向与“x”条目相同的对象。 -
像string和整数这样的对象是不可变的 。 这只是意味着没有方法可以在创build对象后进行更改。 例如,一旦创build了一千个整数对象,它将永远不会改变。 math是通过创build新的整数对象完成的。
-
像列表这样的对象是可变的 。 这意味着对象的内容可以通过指向对象的任何东西来改变。 例如,
x = []; y = x; x.append(10); print y
x = []; y = x; x.append(10); print y
x = []; y = x; x.append(10); print y
将打印[10]
。 空的列表已创build。 “x”和“y”都指向相同的列表。 append方法改变(更新)列表对象(就像向数据库添加一条logging一样),结果对“x”和“y”都是可见的(正如数据库更新对每个数据库的连接都是可见的)。
希望能为你澄清这个问题。
(编辑 – 布莱尔已经更新了他非常stream行的答案,使其现在是准确的)
我认为重要的是要注意,目前得票最多的(布莱尔·康拉德)得票最多,但其结果是正确的,是误导性的,根据其定义是不正确的。 虽然有许多语言(如C)允许用户通过引用传递或通过值传递,但Python不是其中之一。
David Cournapeau的回答指出了真正的答案,并解释了为什么Blair Conrad的post中的行为似乎是正确的,而定义则不正确。
只要Python是按值传递的,所有语言都是按值传递的,因为必须发送一些数据(不pipe是“值”还是“引用”)。 但是,这并不意味着Python是C程序员会想到的,它是一种价值传递。
如果你想要这样的行为,布莱尔·康拉德的答案是好的。 但是如果你想知道为什么Python不是按价值传递或者通过引用传递的细节,请阅读David Cournapeau的答案。
我通常使用的一个简单的技巧就是将它包装在一个列表中:
def Change(self, var): var[0] = 'Changed' variable = ['Original'] self.Change(variable) print variable[0]
(是的,我知道这可能是不方便的,但有时做到这一点很简单。)
Python中没有variables
理解parameter passing的关键是停止思考“variables”。 Python中有名称和对象,它们一起显示为variables,但总是区分这三个variables是有用的。
- Python有名字和对象。
- 赋值将一个名称绑定到一个对象。
- 将parameter passing给函数还会将名称(函数的参数名称)绑定到对象。
这就是全部。 可变性对于这个问题是无关紧要的。
例:
a = 1
这将名称a
绑定到保存值为1的integertypes的对象。
b = x
这将名称b
绑定到与当前绑定的名称x
相同的对象。 之后,名字b
与名字x
无关。
请参阅Python 3语言参考中的第3.1节和第4.2节。
所以在问题中显示的代码中,语句self.Change(self.variable)
将名称var
(在函数Change
中的作用域self.Change(self.variable)
绑定到保存值'Original'
和赋值var = 'Changed'
(在函数体内Change
)再次赋予相同的名称:对其他一些对象(恰好也包含一个string,但可能完全是其他的东西)。
你在这里得到了一些非常好的答案。
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
的variables被赋予一个对self.variable
的引用,并立即将一个string赋值给var
。 它不再指向self.variable
。 下面的代码片断显示了如果修改由var
和self.variable
指向的数据结构(在本例中为list),会发生什么情况:
>>> 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的通过赋值scheme与C ++的引用参数选项并不完全相同,但实际上它与C语言(和其他)的parameter passing模型非常相似:
- 不变的参数实际上是“ 按值 ”传递的 。整数和string等对象是通过对象引用而不是通过复制来传递的,但是因为无论如何你都无法改变不可变的对象,所以效果就像复制一样。
- 可变参数实际上是“ 通过指针 ”传递的 。像列表和字典这样的对象也通过对象引用传递,类似于C将数组作为指针传递的方式 – 可变对象可以在函数中就地改变,就像C数组。
正如你可以说你需要一个可变对象,但是让我build议你检查全局variables,因为它们可以帮助你,甚至解决这种问题!
例:
>>> 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#what-are-the-rules-for-local-and-global-variables-in-python
"In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a new value anywhere within the function's body, it's assumed to be a local. If a variable is ever assigned a new value inside the function, the variable is implicitly local, and you need to explicitly declare it as 'global'. Though a bit surprising at first, a moment's consideration explains this. On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you'd be using global all the time. You'd have to declare as global every reference to a built-in function or to a component of an imported module. This clutter would defeat the usefulness of the global declaration for identifying side-effects."
Even when passing a mutable object to a function this still applies. And to me clearly explains the reason for the difference in behavior between assigning to the object and operating on the object in the function.
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
The assignment to an global variable that is not declared global therefore creates a new local object and breaks the link to the original object.
Here is the simple (I hope) explanation of the concept pass by object
used in Python.
Whenever you pass an object to the function, the object itself is passed (object in Python is actually what you'd call a value in other programming languages) not the reference to this object. In other words, when you call:
def change_me(list): list = [1, 2, 3] my_list = [0, 1] change_me(my_list)
The actual object – [0, 1] (which would be called a value in other programming languages) is being passed. So in fact the function change_me
will try to do something like:
[0, 1] = [1, 2, 3]
which obviously will not change the object passed to the function. If the function looked like this:
def change_me(list): list.append(2)
Then the call would result in:
[0, 1].append(2)
which obviously will change the object. This answer explains it well.
There is a little trick to pass an object by reference, even though the language doesn't make it possible. It works in Java too, it's the list with one item. 😉
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
It's an ugly hack, but it works. ;-P
Aside from all the great explanations on how this stuff works in Python, I don't see a simple suggestion for the problem. As you seem to do create objects and instances, the pythonic way of handling instance variables and changing them is the following:
class PassByReference: def __init__(self): self.variable = 'Original' self.Change() print self.variable def Change(self): self.variable = 'Changed'
In instance methods, you normally refer to self
to access instance attributes. It is normal to set instance attributes in __init__
and read or change them in instance methods. That is also why you pass self
als the first argument to def Change
.
Another solution would be to create a static method like this:
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
I used the following method to quickly convert a couple of Fortran codes to Python. True, it's not pass by reference as the original question was posed, but is a simple work around in some cases.
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
While pass by reference is nothing that fits well into python and should be rarely used there are some workarounds that actually can work to get the object currently assigned to a local variable or even reassign a local variable from inside of a called function.
The basic idea is to have a function that can do that access and can be passed as object into other functions or stored in a class.
One way is to use global
(for global variables) or nonlocal
(for local variables in a function) in a wrapper function.
def change(wrapper): wrapper(7) x = 5 def setter(val): global x x = val print(x)
The same idea works for reading and del
eting a variable.
For just reading there is even a shorter way of just using lambda: x
which returns a callable that when called returns the current value of x. This is somewhat like "call by name" used in languages in the distant past.
Passing 3 wrappers to access a variable is a bit unwieldy so those can be wrapped into a class that has a proxy attribute:
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 "reflection" support makes it possible to get a object that is capable of reassigning a name/variable in a given scope without defining functions explicitly in that scope:
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)
Here the ByRef
class wraps a dictionary access. So attribute access to wrapped
is translated to a item access in the passed dictionary. By passing the result of the builtin locals
and the name of a local variable this ends up accessing a local variable. The python documentation as of 3.5 advises that changing the dictionary might not work but it seems to work for me.
given the way python handles values and references to them, the only way you can reference an arbitrary instance attribute is by name:
class PassByReferenceIsh: def __init__(self): self.variable = 'Original' self.change('variable') print self.variable def change(self, var): self.__dict__[var] = 'Changed'
in real code you would, of course, add error checking on the dict lookup.
Since your example happens to be object-oriented, you could make the following change to achieve a similar result:
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'