Python文档似乎不清楚参数是通过引用还是值传递,以下代码生成未更改的值'Original'
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.change(self.variable)
print(self.variable)
def change(self, var):
var = 'Changed'
有什么我可以通过实际参考传递变量吗?
争论是 通过任务传递。这背后的理由是双重的:
- 传入的参数实际上是a 参考 到一个对象(但引用按值传递)
- 一些数据类型是可变的,但其他数据类型则不可变
所以:
为了更清楚,让我们举一些例子。
列表 - 可变类型
让我们尝试修改传递给方法的列表:
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
方法被称为 - a 命名空间 是为它而创造的;和 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赋值给functions参数。您只能重新分配,但不会更改原始变量值。
Effbot(又名Fredrik Lundh)将Python的变量传递样式描述为call-by-object: 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元素(或任何其他合适的容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上不包含在容器内 - 只有对元素的引用。
我通常使用的一个简单技巧就是将其包装在一个列表中:
def Change(self, var):
var[0] = 'Changed'
variable = ['Original']
self.Change(variable)
print variable[0]
(是的,我知道这可能很不方便,但有时这很简单。)
(编辑 - 布莱尔更新了他非常受欢迎的答案,现在它已经准确了)
我认为值得注意的是,目前投票率最高的帖子(布莱尔康拉德)虽然在结果方面是正确的,但却是误导性的,并且基于其定义是不正确的。虽然有许多语言(如C)允许用户通过引用传递或按值传递,但Python不是其中之一。
David Cournapeau的回答指出了真正的答案,并解释了为什么布莱尔康拉德的帖子中的行为似乎是正确的,而定义则不然。
在Python按值传递的范围内,所有语言都是按值传递的,因为必须发送一些数据(无论是“值”还是“引用”)。但是,这并不意味着Python在C程序员会想到它的意义上是通过值传递的。
如果你想要这种行为,布莱尔康拉德的回答很好。但是如果你想知道为什么Python既没有通过值传递也没有通过引用传递,那么请阅读David Cournapeau的回答。