Python文档似乎不清楚参数是通过引用还是值传递,以下代码生成未更改的值'Original'
class PassByReference: def __init__(self): self.variable = 'Original' self.change(self.variable) print(self.variable) def change(self, var): var = 'Changed'
有什么我可以通过实际参考传递变量吗?
参数通过赋值传递.这背后的理由是双重的:
传递的参数实际上是一个参考的一个对象(但参考通过值传递)
一些数据类型是可变的,但其他数据类型则不可变
所以:
如果你将一个可变对象传递给一个方法,那么该方法会获得对同一个对象的引用,你可以将它改变为你心中的喜悦,但是如果你在方法中重新引用引用,那么外部范围将对它一无所知,之后你完成后,外部引用仍将指向原始对象.
如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至无法改变对象.
为了更清楚,让我们举一些例子.
让我们尝试修改传递给方法的列表:
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])
虽然这看起来有点麻烦.
问题来自对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处理变量和参数的方式.
它既不是按值传递,也不是按引用传递 - 它是逐个调用的.请见Fredrik Lundh:
http://effbot.org/zone/call-by-object.htm
这是一个重要的引用:
"......变量[名称] 不是对象;它们不能用其他变量表示或由对象引用."
在您的示例中,Change
调用方法时- 为其创建名称空间 ; 并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"时x = 2000
,将创建一个新的整数对象,并更新字典以指向新对象.旧的一千个对象没有变化(可能存在也可能不存在,具体取决于是否有其他任何东西引用该对象).
当您执行新的分配时y = x
,会创建一个新的字典条目"y",该条目指向与"x"条目相同的对象.
像字符串和整数这样的对象是不可变的.这只是意味着没有方法可以在创建对象后更改对象.例如,一旦创建了整数对象一千,它就永远不会改变.通过创建新的整数对象来完成数学.
像列表这样的对象是可变的.这意味着可以通过指向对象的任何内容来更改对象的内容.例如,x = []; y = x; x.append(10); print y
将打印[10]
.空列表已创建."x"和"y"都指向同一个列表.该追加方法变异(更新)列表中的对象(如添加一条记录到数据库),结果是这两个"X"和"Y"看得见的(就像一个数据库更新将每一个连接到该数据库中可见).
希望能为您澄清问题.
从技术上讲,Python总是使用传递引用值.我将重复我的其他答复以支持我的发言.
Python始终使用传递引用值.没有任何例外.任何变量赋值都意味着复制参考值.没有例外.任何变量都是绑定到参考值的名称.总是.
您可以将参考值视为目标对象的地址.使用时会自动取消引用该地址.这样,使用参考值,您似乎直接使用目标对象.但总会有一个参考,更多的是跳到目标.
下面是证明Python使用引用传递的示例:
如果参数是按值传递的,则lst
无法修改外部参数.绿色是目标对象(黑色是存储在内部的值,红色是对象类型),黄色是内部具有参考值的内存 - 绘制为箭头.蓝色实线箭头是传递给函数的参考值(通过虚线蓝色箭头路径).丑陋的深黄色是内部字典.(它实际上也可以画成绿色椭圆.颜色和形状只表示它是内部的.)
您可以使用id()
内置函数来了解参考值(即目标对象的地址).
在编译语言中,变量是能够捕获类型值的内存空间.在Python中,变量是绑定到引用变量的名称(内部捕获为字符串),该引用变量保存目标对象的引用值.变量的名称是内部字典中的键,该字典项的值部分将参考值存储到目标.
参考值隐藏在Python中.没有任何显式用户类型用于存储参考值.但是,您可以使用list元素(或任何其他合适的容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用.换句话说,元素实际上不包含在容器内 - 只有对元素的引用.
理解参数传递的关键是停止思考"变量".Python中有名称和对象,它们一起看起来像变量,但总是区分三者是有用的.
Python有名称和对象.
赋值将名称绑定到对象.
将参数传递给函数还会将名称(函数的参数名称)绑定到对象.
这就是它的全部.可变性与这个问题无关.
例:
a = 1
这会将名称绑定a
到包含值1的整数类型的对象.
b = x
这会将名称绑定到b
名称x
当前绑定的同一对象.之后,名称与名称b
无关x
.
请参阅Python 3语言参考中的3.1和4.2节.
因此,在问题中显示的代码中,语句self.Change(self.variable)
将名称var
(在函数范围内Change
)绑定到保存值的对象,'Original'
并且赋值var = 'Changed'
(在函数体中Change
)再次分配相同的名称:到其他对象(即碰巧也会持有一个字符串但完全可能是其他东西.
我通常使用的一个简单技巧就是将其包装在一个列表中:
def Change(self, var): var[0] = 'Changed' variable = ['Original'] self.Change(variable) print variable[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语言(和其他语言)的参数传递模型非常相似:
不可变参数有效地通过" 值 " 传递.整数和字符串等对象通过对象引用而不是通过复制传递,但是因为无论如何都无法在原地更改不可变对象,效果就像制作副本一样.
可变参数有效地通过" 指针 " 传递.列表和字典之类的对象也通过对象引用传递,这类似于C传递数组的方式作为指针 - 可变对象可以在函数中就地更改,就像C数组一样.
你可以说你需要有一个可变对象,但是我建议你检查全局变量,因为它们可以帮助你甚至解决这类问题!
http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python
例:
>>> def x(y): ... global z ... z = y ... >>> x>>> y Traceback (most recent call last): File " ", line 1, in NameError: name 'y' is not defined >>> z Traceback (most recent call last): File " ", line 1, in NameError: name 'z' is not defined >>> x(2) >>> x >>> y Traceback (most recent call last): File " ", line 1, in 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
因此,对未声明为全局变量的全局变量的赋值会创建一个新的本地对象,并断开与原始对象的链接.
以下是pass by object
Python中使用的概念的简单(我希望)解释.
无论何时将对象传递给函数,对象本身都会被传递(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)
这显然会改变对象.这个答案解释得很好.
除了关于这些东西如何在Python中运行的所有重要解释之外,我没有看到针对该问题的简单建议.当您似乎创建对象和实例时,处理实例变量并更改它们的pythonic方法如下:
class PassByReference: def __init__(self): self.variable = 'Original' self.Change() print self.variable def Change(self): self.variable = 'Changed'
在实例方法中,您通常会引用self
访问实例属性.__init__
在实例方法中设置实例属性并读取或更改它们是正常的.这也是你传递self
第一个参数的原因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