python - 如何将一个变量通过引用传递?

  显示原文与译文双语对照的内容

python 文档似乎不清楚参数是通过引用还是按值传递,下面的代码生成未更改的值'原始''


class PassByReference:
 def __init__(self):
 self.variable = 'Original'
 self.Change(self.variable)
 print self.variable

 def Change(self, var):
 var = 'Changed'

我可以通过实际引用来传递变量?

时间:

参数都是以赋值传递, 背后的基本原理是双重的:

  1. 传入的参数实际上是一个对象( 但引用是通过值传递的)的引用
  2. 某些数据类型是可变的,但其他数据类型不是

所以:

  • 如果将可变对象传递到方法,方法获得对同一个对象的引用,你可以将它的变异为心形,但如果你在方法中重新绑定引用,外部引用仍然会指向原始对象。

  • 如果一个不可变对象传递给某个方法,你仍然不能重新绑定外部参照,你都看不到改变或者替换该对象。

为了让它更加清晰,我们来举一些例子。

列表- 一个可变类型

让我们尝试修改传递给方法的列表:


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_listouter_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_stringouter_string 引用的一个副本,我们有 the_string 指向一个新字符串,但是没有办法改变 outer_string 指向的位置。

我希望这能稍微清理一下。

编辑: 注意到这并没有回答 @David 最初询问的问题,"我可以通过实际引用来传递变量"。 让我们来看看。

我们怎么绕过这个?

就像 @Andrea's 回答所示,你可以返回新值。 这不会改变传入的方式,但会让你得到想要的信息:


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 不再引用第一个对象,这两个对象仍然可以继续共存;实际上,它们可以由程序中的任意数量的其他引用共享。

调用带有参数的函数时,会创建一个引用传递给。 这与函数调用中使用的引用不同,因此无法更新引用并使它的引用新对象。 在你的示例中:


 self.variable = 'Original'
 self.Change(self.variable)

def Change(self, var):
 var = 'Changed'

self.variable 是对字符串对象 'Original'的引用。 调用 Change 时,为对象创建第二个引用 var 。 在函数内,将引用 var 重新分配给另一个字符串对象 'Changed',但引用 self.variable 是独立的并且不改变。

唯一的方法是传递一个可变对象。 因为两个引用引用同一个对象,对对象的任何更改都反映在两个位置。


 self.variable = ['Original']
 self.Change(self.variable)

def Change(self, var):
 var[0] = 'Changed'

它既不是pass-by-value也不是 pass-by-reference - 它是 call-by-object 。 通过 Fredrik Lundh查看这一点:

http://effbot.org/zone/call-by-object.htm

下面是一个重要的报价:

"。。变量 [names] 不是对象;它们不能由其他变量或者对象引用。"

在你的示例中,当 Change 方法为 called--a namespace is created for it; 并且 var 成为名称时,在该命名空间中,对于字符串对象 'Original' 。 对象在两个命名空间中有一个名称。 接下来,var ='Changed'var 绑定到一个新的字符串对象,因此方法的名称空间忘记了 'Original' 。 最后,该名称空间被遗忘,并且字符串 'Changed' 随它一起。

想的东西也被通过引用被赋值 代替/通过值传递。 这样,allways就会很清晰,只要你明白在正常工作分配过程中发生了什么。

因此,当将列表传递给函数/方法时,列表被分配给参数名。 附加到列表将导致列表被修改。 将列表中的 。列表重新分配不会改变原始列表,因为:


a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b # prints [1, 2, 3, 4] ['a', 'b']

因为不可变类型无法修改,他们从互联网似乎喜欢被按值传递- 传递一个整数到一个功能主要是注册到函数的参数。 你只能重新指定它,但它不会改变originial变量的值。

我发现所有其他的答案长又复杂,所以我创建了这个简单的图表来解释 python 处理变量和参数的方式。 Python pass by object

从技术上讲,python 总是使用通过引用值 。 我将重复的其他答案来支持我的语句。

python 总是使用pass-by-reference值。 没有例外。任何变量赋值意味着复制引用值。 不例外。任何变量都是绑定到引用值的名称。 总是。

你可以将引用值看作目标对象的地址。 使用时自动取消对地址的引用。 这样,使用引用值,就可以直接与目标对象一起工作。 但在两者之间总是有一个引用,一步一步跳转到目标。

下面是一个示例,证明 python 使用引用传递:

Illustrated example of passing the argument

如果参数是按值传递的,则不能修改外部 lst 。 绿色是目标对象( 黑色是存储在里面的值,红色是对象类型),黄色是带参考值的内存,在--中绘制为箭头。 蓝色实心箭头是传递给函数( 通过虚线蓝色箭头路径)的引用值。 丑陋的暗黄色是内部字典。 ( 实际上也可以绘制为绿色椭圆) 。 颜色和形状只表示它是内部的。

你可以使用 id() 内置函数来了解引用值是什么。

在编译语言中,变量是能够捕获类型值的内存空间。 在 python 中,变量是一个名称( 作为字符串内部捕获),它绑定到引用变量,该变量将引用值保存到目标对象。 变量的名称是内部字典中的键,字典项的值部分将引用值存储到目标。

引用值在 python 中隐藏。 没有用于存储引用值的显式用户类型。 但是,可以使用列表元素( 或者其他任何合适的容器类型中的元素) 作为引用变量,因为所有容器都将元素存储为对目标对象的引用。 换句话说,元素实际上不包含在容器--中只包含对元素的引用。

Effbot ( 又名 Fredrik Lundh ) 描述了 python 传递样式作为call-by-object的变量: http://effbot.org/zone/call-by-object.htm

对象在堆上分配,指向它们的指针可以在任何地方传递。

  • 当你进行赋值( 如 x = 1000 ) 时,会创建一个字典条目,将当前命名空间中的字符串"x"映射到一个指向包含千位对象的整数对象的指针。

  • 之后,在新object,将更新与 x = 2000"x",创建一个新的整数对象,字典已经更新为单独存在 旧的one对象不变( 并且可能是否存在,取决于是否有其他对象引用对象) 。

  • 当你执行新的赋值( 比如 y = x ) 时,会创建一个新的字典条目"y",它指向与"x"条目相同的对象。

  • 对象,如字符串和数值直接不可变 。 这就意味着在创建对象之后没有任何方法可以改变对象。 例如一旦创建了整数对象 one-thousand,它将永远不会改变。 数学是通过创建新的整数对象完成的。

  • 像列表一样的对象是可变 。 这意味着对象的内容可以通过任何指向对象的内容来改变。 比如, x = []; y = x; x.append(10); print y 将打印 [10] 。创建了空列表。 "x"和"y"指向同一列表。 附加方法改变( 更新)的列表对象( 像向数据库添加记录一样) 和结果对"x"和"y"都可见( 如同数据库更新对该数据库的每个连接都可见) 。

希望为你澄清这个问题。

在它的definitions,上我认为它需要注意的是,目前的职位相关的大多数投票( by 。Conrad ) 门,当正确的对于它的结果,是误导人的和是大腿based.不正确 虽然有许多语言( 像C ) 既,允许用户在通过引用传递还是通过值传递,python 并不是其中之一。

大卫回答( cournapeau ) 指向详细的答案和解释了为什么该行为在Blair担任康拉德似乎是正确的,而这些定义则不然。

如果 python 通过值传递,所有语言都会通过值传递,因为某些数据( 是一个"值"或者a"引用") 必须被发送。 然而,这并不意味着它的python 按值传递在这个意义上,它是一个C 程序员可能这么想。

如果你想要这样的行为,Blair的回答就很好。 但如果你想知道的具体细节为什么 python cournapeau的既不是按值传递或者按引用传递,请阅读大卫回答。

我通常使用的一个简单技巧就是将它包装在列表中:


def Change(self, var):
 var[0] = 'Changed'

variable = ['Original']
self.Change(variable) 
print variable[0]

( 是的,我知道这很不方便,但有时也很简单。)

理解参数传递的关键是停止思考"变量"。 在没有 variables.

  1. python 有名称和对象。
  2. 赋值将名称绑定到对象。
  3. 将参数传递给函数也将名称( 函数的参数名) 绑定到对象。

这就是它的全部。 可变性与这个问题无关。

例如:

 
a = 1

 

这将名称 a 绑定到一个包含值 1的类型的对象。

 
b = x

 

这将名称 b 绑定到名称 x 当前绑定到的同一对象。 之后,名称 b 与名称 x 无关。

在 python reference, 3语言请参见章节 3.1和


因此,在问题中显示的代码中,语句 self.Change(self.variable) 将名称 var ( 在函数 Change的作用域内) 绑定到保存值 'Original'的对象,赋值 var ='Changed' ( 在函数 Change的主体中) 再次指定相同的名称: 到其他对象( 这也恰好包含了一个字符串,但可能是其他的) 。

...