Python文档似乎不清楚参数是通过引用还是值传递,以下代码生成未更改的值'Original'
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.change(self.variable)
print(self.variable)
def change(self, var):
var = 'Changed'
有什么我可以通过实际参考传递变量吗?
争论是passed by assignment。这背后的理由是双重的:
所以:
为了更清楚,让我们举一些例子。
让我们尝试修改传递给方法的列表:
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
是outer_list
参考的副本,我们让the_list
指向一个新的列表,但没有办法改变outer_list
指向的地方。
它是不可变的,所以我们无法改变字符串的内容
现在,让我们尝试更改引用
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
参数是通过值传递的,因此为其分配新字符串不会影响方法外部的代码。 the_string
是outer_string
参考的副本,我们有the_string
指向一个新的字符串,但没有办法改变outer_string
指向的地方。
我希望这可以解决一些问题。
编辑:有人指出,这并没有回答@David最初提出的问题,“我能做些什么来通过实际参考传递变量?”。让我们继续努力。
正如@ 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)
如果你真的想避免使用返回值,你可以创建一个类来保存你的值并将它传递给函数或使用现有的类,如列表:
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])
虽然这看起来有点麻烦。
(编辑 - Blair更新了他非常受欢迎的答案,现在它已经准确了)
我认为值得注意的是,目前投票率最高的帖子(布莱尔康拉德)虽然在结果方面是正确的,但却具有误导性,并且基于其定义是不正确的。虽然有许多语言(如C)允许用户通过引用传递或按值传递,但Python不是其中之一。
David Cournapeau回答了真正的答案,并解释了为什么布莱尔康拉德的帖子中的行为似乎是正确的,而定义则不然。
在Python通过值传递的范围内,所有语言都是按值传递的,因为必须发送一些数据(无论是“值”还是“引用”)。但是,这并不意味着Python在C程序员会想到它的意义上是通过值传递的。
如果你想要这种行为,布莱尔康拉德的回答很好。但是如果你想知道为什么Python既没有通过价值传递也没有通过引用传递,那么请阅读David Cournapeau的回答。
你在这里得到了一些非常好的答案。
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' )
在这种情况下,方法var
中名为Change
的变量被赋予对self.variable
的引用,并立即为var
指定一个字符串。它不再指向self.variable
。以下代码片段显示了如果修改var
和self.variable
指向的数据结构会发生什么,在本例中是一个列表:
>>> 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-by-assignment方案与C ++的引用参数选项并不完全相同,但事实证明它与C语言(和其他语言)的参数传递模型非常相似:
你可以说你需要有一个可变对象,但是我建议你检查全局变量,因为它们可以帮助你甚至解决这类问题!
例:
>>> 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
“在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中使用的pass by object
概念的简单(我希望)解释。
无论何时将对象传递给函数,对象本身都会被传递(Python中的对象实际上是您在其他编程语言中称为值的对象),而不是对该对象的引用。换句话说,当你打电话:
def change_me(list):
list = [1, 2, 3]
my_list = [0, 1]
change_me(my_list)
正在传递实际对象 - [0,1](在其他编程语言中称为值)。事实上,change_me
函数会尝试做类似的事情:
[0, 1] = [1, 2, 3]
这显然不会改变传递给函数的对象。如果函数看起来像这样:
def change_me(list):
list.append(2)
然后调用将导致:
[0, 1].append(2)
这显然会改变对象。 This answer解释得很好。
除了关于这些东西如何在Python中运行的所有重要解释之外,我没有看到针对该问题的简单建议。当您似乎创建对象和实例时,处理实例变量并更改它们的pythonic方法如下:
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.Change()
print self.variable
def Change(self):
self.variable = 'Changed'
在实例方法中,通常引用self
来访问实例属性。在__init__
中设置实例属性并在实例方法中读取或更改它们是正常的。这也是为什么你把self
als传递给def Change
的第一个参数。
另一个解决方案是创建一个这样的静态方法:
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,它是带有一个项目的列表。 ;-)
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。确实,它并没有通过引用传递,因为提出了原始问题,但在某些情况下这是一个简单的工作。
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
问题来自对Python中变量的误解。如果您习惯于大多数传统语言,那么您将拥有以下序列中发生的事情的心理模型:
a = 1
a = 2
您认为a
是存储值1
的内存位置,然后更新以存储值2
。这不是Python中的工作方式。相反,a
作为对具有值1
的对象的引用开始,然后被重新分配为对具有值2
的对象的引用。即使a
不再引用第一个对象,这两个对象可能会继续共存;实际上,它们可能被程序中的任何其他引用共享。
当您使用参数调用函数时,会创建一个引用传入对象的新引用。这与函数调用中使用的引用是分开的,因此无法更新该引用并使其引用新对象。在你的例子中:
def __init__(self):
self.variable = 'Original'
self.Change(self.variable)
def Change(self, var):
var = 'Changed'
self.variable
是对字符串对象'Original'
的引用。当你调用Change
时,你会为对象创建第二个参考var
。在函数内部,您将参考var
重新分配给不同的字符串对象'Changed'
,但参考self.variable
是独立的,不会更改。
解决这个问题的唯一方法是传递一个可变对象。因为两个引用都引用同一个对象,所以对象的任何更改都会反映在两个位置。
def __init__(self):
self.variable = ['Original']
self.Change(self.variable)
def Change(self, var):
var[0] = 'Changed'
虽然通过引用传递并不适合python并且应该很少使用,但实际上可以使用一些变通方法来获取当前分配给局部变量的对象,甚至可以从被调用函数内部重新分配局部变量。
基本思想是拥有一个可以进行访问的函数,可以作为对象传递给其他函数或存储在类中。
一种方法是在包装函数中使用global
(用于全局变量)或nonlocal
(用于函数中的局部变量)。
def change(wrapper):
wrapper(7)
x = 5
def setter(val):
global x
x = val
print(x)
同样的想法适用于阅读和del
e变量。
对于刚刚阅读,甚至有一种更简单的方法就是使用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
的属性访问被转换为传递的字典中的项访问权限。通过传递内置locals
的结果和局部变量的名称,这最终会访问局部变量。从3.5开始的python文档建议更改字典可能不起作用,但它似乎对我有用。
给定python处理值和引用它们的方式,可以引用任意实例属性的唯一方法是按名称:
class PassByReferenceIsh:
def __init__(self):
self.variable = 'Original'
self.change('variable')
print self.variable
def change(self, var):
self.__dict__[var] = 'Changed'
在实际代码中,您当然会在dict查找中添加错误检查。
由于您的示例恰好是面向对象的,因此您可以进行以下更改以获得类似的结果:
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'
Python中的Pass-By-Reference与C ++ / Java中的引用传递概念完全不同。
您只能使用空类作为实例来存储引用对象,因为内部对象属性存储在实例字典中。查看示例。
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)
由于字典是通过引用传递的,因此您可以使用dict变量在其中存储任何引用的值。
# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
result = a + b
ref['multi'] = a * b # reference the multi. ref['multi'] is number
ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
return result # return the sum
number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.
sum = AddNumbers(number1, number2, ref)
print("sum: ", sum) # the return value
print("multi: ", ref['multi']) # a referenced value
print("msg: ", ref['msg']) # a referenced value
因为似乎没有提到模拟参考文献的方法,例如从C ++是使用“更新”函数并传递它而不是实际变量(或者更确切地说,“名称”):
def need_to_modify(update):
update(42) # set new value 42
# other code
def call_it():
value = 21
def update_value(new_value):
nonlocal value
value = new_value
need_to_modify(update_value)
print(value) # prints 42
这对于“仅限引用”或具有多个线程/进程的情况(通过使更新函数线程/多处理安全)非常有用。
显然上面的内容不允许读取值,只是更新它。
它既不是按值传递,也不是按引用传递 - 它是逐个调用的。请见Fredrik Lundh:
http://effbot.org/zone/call-by-object.htm
这是一个重要的引用:
“......变量[名称]不是对象;它们不能用其他变量表示或由对象引用。”
在你的例子中,当调用Change
方法时 - 为它创建一个namespace;和var
成为该命名空间中的字符串对象'Original'
的名称。然后,该对象在两个名称空间中具有名称。接下来,var = 'Changed'
将var
绑定到一个新的字符串对象,因此该方法的命名空间忘记了'Original'
。最后,忘记了该命名空间,并将字符串'Changed'
与它一起使用。
想想通过赋值而不是通过引用/通过值传递的东西。这样,只要您了解正常分配期间发生的情况,就可以清楚地知道发生了什么。
因此,将列表传递给函数/方法时,会将列表分配给参数名称。附加到列表将导致列表被修改。重新分配函数内的列表不会更改原始列表,因为:
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”映射到指向包含一千个整数对象的指针。x = 2000
更新“x”时,将创建一个新的整数对象,并更新字典以指向新对象。旧的一千个对象没有变化(可能存在也可能不存在,具体取决于是否有其他任何东西引用该对象)。y = x
等新分配时,会创建一个新词典条目“y”,该词条目指向与“x”条目相同的对象。x = []; y = x; x.append(10); print y
将打印[10]
。空列表已创建。 “x”和“y”都指向同一个列表。 append方法改变(更新)列表对象(如向数据库添加记录),结果对“x”和“y”都可见(正如数据库更新对于该数据库的每个连接都是可见的)。希望能为您澄清问题。
从技术上讲,Python总是使用传递引用值。我将重复my other answer来支持我的发言。
Python始终使用传递引用值。没有任何例外。任何变量赋值都意味着复制参考值。没有例外。任何变量都是绑定到参考值的名称。总是。
您可以将参考值视为目标对象的地址。使用时会自动取消引用该地址。这样,使用参考值,您似乎直接使用目标对象。但总会有一个参考,更多的是跳到目标。
下面是证明Python使用引用传递的示例:
如果参数是按值传递的,则无法修改外部lst
。绿色是目标对象(黑色是存储在内部的值,红色是对象类型),黄色是内部具有参考值的内存 - 绘制为箭头。蓝色实线箭头是传递给函数的参考值(通过虚线蓝色箭头路径)。丑陋的深黄色是内部字典。 (它实际上也可以画成绿色椭圆。颜色和形状只表示它是内部的。)
您可以使用id()
内置函数来了解参考值(即目标对象的地址)。
在编译语言中,变量是能够捕获类型值的内存空间。在Python中,变量是绑定到引用变量的名称(内部捕获为字符串),该引用变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分将参考值存储到目标。
参考值隐藏在Python中。没有任何显式用户类型用于存储参考值。但是,您可以使用list元素(或任何其他合适的容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上不包含在容器内 - 只有对元素的引用。
我通常使用的一个简单技巧就是将其包装在一个列表中:
def Change(self, var):
var[0] = 'Changed'
variable = ['Original']
self.Change(variable)
print variable[0]
(是的,我知道这可能很不方便,但有时这很简单。)
理解参数传递的关键是停止思考“变量”。 Python中有名称和对象,它们一起看起来像变量,但总是区分三者是有用的。
这就是它的全部。可变性与这个问题无关。
例:
a = 1
这将名称a
绑定到一个包含值1的整数类型的对象。
b = x
这将名称b
绑定到名称x
当前绑定的同一对象。之后,名称b
与x
名称无关。
在问题中显示的代码中,语句self.Change(self.variable)
将名称var
(在函数Change
的范围内)绑定到保存值'Original'
的对象,赋值var = 'Changed'
(在函数体Change
中)再次指定相同的名称:to一些其他对象(碰巧也包含一个字符串,但完全可能是其他东西)。
(编辑2019-04-28)
因此,如果您想要更改的内容是可变对象,则没有问题,因为所有内容都通过引用有效传递。
如果它是一个immutable对象(例如bool,number,string),那么可以将它包装在一个可变对象中。
对此的快速和肮脏的解决方案是一个单元素列表(而不是self.variable
,传递[self.variable]
和函数修改var[0]
)。
pythonic方法越多,就会引入一个简单的单属性类。该函数接收类的实例并操纵该属性。