如何通过引用传递变量?

How do I pass a variable by reference?

提问人:David Sykes 提问时间:6/12/2009 最后编辑:Karl KnechtelDavid Sykes 更新时间:7/13/2023 访问量:2024375

问:

我写了这个类来测试:

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

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

当我尝试创建实例时,输出是 .因此,Python 中的参数似乎是按值传递的。这是对的吗?如何修改代码以获得按引用传递的效果,以便输出是?OriginalChanged


有时人们会惊讶地发现,像 x = 1 这样的代码(其中 x 是参数名称)不会影响调用者的参数,但像 x[0] = 1 这样的代码却会影响调用方的参数。发生这种情况的原因是,尽管有 = 语法,但项赋值和切片赋值是改变现有对象的方法,而不是重新赋值变量。有关详细信息,请参阅为什么函数可以修改调用方感知到的某些参数,而不能修改其他参数?

另请参阅按引用传递与按值传递有什么区别?,了解重要的、与语言无关的术语讨论。

Python 引用 参数 - 传递 传递

评论

11赞 Ethan Furman 1/7/2012
BlairConrad 的回答中的代码很好,但 DavidCournapeau 和 DarenThomas 提供的解释是正确的。
79赞 lqc 11/15/2012
在阅读所选答案之前,请考虑阅读这篇简短的文本 其他语言有“变量”,Python 有“名称”。考虑“名称”和“对象”而不是“变量”和“引用”,您应该避免很多类似的问题。
32赞 PhilS 6/12/2009
有关简短的解释/说明,请参阅此 stackoverflow 问题的第一个答案。由于字符串是不可变的,因此它们不会被更改,并且会创建一个新变量,因此“外部”变量仍然具有相同的值。
4赞 Abraham Sangha 4/23/2020
工作链接:其他语言有“变量”,Python有“名称”
13赞 Ray Hulha 6/10/2020
Iqc链接的新官方方式:david.goodger.org/projects/pycon/2007/idiomatic/...

答:

22赞 Mike Mazur 6/12/2009 #1

在本例中,为方法中标题的变量分配了对 的引用,并立即将字符串分配给 。它不再指向 .以下代码片段显示了如果修改 和 指向的数据结构,在本例中为列表,会发生什么情况:varChangeself.variablevarself.variablevarself.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']
>>> 

我相信其他人可以进一步澄清这一点。

3514赞 Blair Conrad 6/12/2009 #2

参数通过赋值传递。这背后的基本原理有两个:

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

所以:

  • 如果将可变对象传递到方法中,则该方法将获得对同一对象的引用,您可以随心所欲地对其进行更改,但是如果您在方法中重新绑定引用,则外部作用域将对此一无所知,并且在完成之后,外部引用仍将指向原始对象。

  • 如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至无法更改对象。

为了更清楚,让我们举一些例子。

List - 可变类型

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

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']

由于传入的参数是对 的引用,而不是它的副本,因此我们可以使用 mutating list 方法来更改它,并将更改反映在外部作用域中。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_listthe_listouter_listthe_listouter_list

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_stringthe_stringouter_stringthe_stringouter_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])

虽然这似乎有点麻烦。

评论

205赞 Andrea Ambu 6/12/2009
那么在 C 中也是如此,当你传递“通过引用”时,你实际上是在通过值传递引用......定义“按引用”:P
123赞 Blair Conrad 6/12/2009
我不确定我是否理解你的条款。我已经离开 C 游戏一段时间了,但当我进入 C 游戏时,没有“通过引用传递”——你可以传递东西,而且它总是按值传递,所以参数列表中的任何内容都被复制了。但有时它是一个指针,人们可以跟随它到内存片段(原始、数组、结构等),但你不能改变从外部作用域复制的指针——当你完成这个函数时,原来的指针仍然指向同一个地址。C++ 引入了引用,其行为不同。
46赞 Cam Jackson 9/9/2011
@Zac 保龄球 我真的不明白你说的在实际意义上与这个答案有什么关系。如果 Python 新手想知道如何传递 ref/val,那么这个答案的要点是: 1- 您可以使用函数接收的引用作为其参数来修改变量的“外部”值,只要您不重新分配参数以引用新对象。阿拉伯数字-分配给不可变类型将始终创建一个新对象,这会中断对外部变量的引用。
16赞 Mark Ransom 11/16/2011
@CamJackson,你需要一个更好的例子——数字在 Python 中也是不可变的对象。此外,如果说任何在等号左侧没有下标的赋值都会将名称重新赋给一个新对象,无论它是否不可变,这难道不是真的吗? 不会从调用方的角度修改列表的内容。def Foo(alist): alist = [1,2,3]
74赞 Ethan Furman 1/7/2012
-1.显示的代码很好,关于如何的解释是完全错误的。请参阅DavidCournapeau或DarenThomas的答案,了解原因的正确解释。
28赞 bobobobo 6/12/2009 #3

你在这里得到了一些非常好的答案。

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' )

评论

3赞 laycat 6/29/2014
是的,但是如果你做 x = [ 2, 4, 4, 5, 5], y = x, X[0] = 1 , print x # [1, 4 ,4, 5, 5] print y # [1, 4, 4, 5, 5]
0赞 pippo1980 8/1/2021
x[0] 还是 x[0] ?不明白
224赞 Daren Thomas 6/12/2009 #4

想想东西是通过赋值传递的,而不是通过引用/值传递的。这样,只要您了解正常任务期间发生的情况,就可以始终清楚发生了什么。

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

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

由于不可变类型无法修改,因此它们似乎是按值传递的 - 将 int 传递到函数中意味着将 int 分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。

评论

11赞 Christian Groleau 11/23/2017
乍一看,这个答案似乎回避了最初的问题。第二次阅读后,我意识到这让事情变得很清楚。这个“名称分配”概念的一个很好的后续可以在这里找到: 像 Pythonista 一样编码:惯用的 Python
0赞 Egret 11/17/2023
那么,是否可以概括为:赋值一个不可变的类型本质上是重新赋值它,因为赋值一个无法修改的类型是没有意义的?
276赞 David Cournapeau 6/12/2009 #5

它既不是按值传递,也不是按引用传递,而是按对象调用。请看弗雷德里克·伦德(Fredrik Lundh)的这篇文章:

按对象调用

这是一个重要的引述:

"...变量 [names] 不是对象;它们不能用其他变量来表示,也不能用对象来指代。

在您的示例中,当调用该方法时,将为其创建一个命名空间;并成为该命名空间中 String 对象的名称。然后,该对象在两个命名空间中具有名称。接下来,绑定到一个新的字符串对象,因此该方法的命名空间会忘记 .最后,该命名空间被遗忘,字符串也随之被遗忘。Changevar'Original'var = 'Changed'var'Original''Changed'

评论

25赞 Luciano 12/13/2011
我发现很难买到。对我来说,就像 Java 一样,参数是指向内存中对象的指针,这些指针通过堆栈或寄存器传递。
10赞 David Cournapeau 12/14/2011
这不像 java。其中一种情况是不可变对象。想想平凡的函数 lambda x: x。将此选项应用于 x = [1, 2, 3] 和 x = (1, 2, 3)。在第一种情况下,返回的值将是输入的副本,在第二种情况下是相同的。
34赞 Mike Graham 11/15/2012
不,它完全像 Java 的对象语义。我不确定你所说的“在第一种情况下,返回的值将是输入的副本,在第二种情况下是相同的”是什么意思,但这种说法似乎显然是不正确的。
28赞 cayhorstmann 12/20/2012
它与 Java 完全相同。对象引用按值传递。任何有不同想法的人都应该为一个可以交换两个引用的函数附加 Python 代码,如下所示:swapa = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
28赞 Claudiu 7/18/2013
当您在 Java 中传递对象时,它与 Java 完全相同。但是,Java 也有基元,这些基元是通过复制基元的值来传递的。因此,在这种情况下它们有所不同。
60赞 AmanicA 8/6/2011 #6

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

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

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

(是的,我知道这可能会很不方便,但有时这样做很简单。

评论

9赞 Justas 2/1/2015
好。要传递 ref,请换行 [ ]'s。
889赞 Mark Ransom 11/16/2011 #7

问题来自对 Python 中变量的误解。如果你习惯了大多数传统语言,你就会对以下顺序发生的事情有一个心智模型:

a = 1
a = 2

您认为这是存储值的内存位置,然后更新以存储该值。这不是 Python 中的工作方式。相反,它以对具有 值的对象的引用开始,然后被重新赋值为对具有 的对象的引用。这两个对象可以继续共存,即使不再指第一个对象;事实上,它们可以由程序中任意数量的其他引用共享。a12a12a

使用参数调用函数时,将创建一个引用传入的对象的新引用。这与函数调用中使用的引用是分开的,因此无法更新该引用并使其引用新对象。在您的示例中:

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

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

self.variable是对 String 对象的引用。调用时,将创建对对象的第二个引用。在函数中,您将引用重新分配给不同的字符串对象,但引用是独立的,不会更改。'Original'Changevarvar'Changed'self.variable

解决此问题的唯一方法是传递一个可变对象。由于两个引用都引用同一个对象,因此对对象的任何更改都会反映在两个位置。

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

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

评论

132赞 Cam Jackson 11/16/2011
很好的简洁解释。您的段落“当您调用函数时...”是我听说过的最好的解释之一,关于“Python 函数参数是引用,按值传递”这个相当隐晦的短语。我认为,如果你只理解那一段,其他一切都是有道理的,并从那里得出合乎逻辑的结论。然后,您只需要注意何时创建新对象以及何时修改现有对象即可。
4赞 Kashif 5/7/2012
但是,如何重新分配引用呢?我以为你不能改变'var'的地址,但你的字符串'Changed'现在将存储在'var'内存地址中。您的描述使“已更改”和“原始”看起来属于内存中的不同位置,而您只需将“var”切换到不同的地址即可。这是对的吗?
11赞 Mark Ransom 5/7/2012
@Glassjawed,我想你明白了。“Changed”和“Original”是位于不同内存地址的两个不同的字符串对象,并且“var”从指向一个变为指向另一个。
2赞 Tony Suffolk 66 3/10/2018
函数调用不会创建新的引用 - 使用函数内部和外部的 id 函数来确认这一点。区别在于当您尝试在函数中更改对象时,对象会发生什么情况。
4赞 Mark Ransom 3/10/2018
@TonySuffolk66 提供引用对象的标识,而不是引用本身。id
68赞 pepr 9/16/2012 #8

从技术上讲,Python 始终使用按引用传递值。我将重复我的另一个答案来支持我的发言。

Python 始终使用按引用传递值。没有任何例外。任何变量赋值都意味着复制参考值。也不例外。任何变量都是绑定到引用值的名称。总是。

您可以将引用值视为目标对象的地址。使用时,该地址会自动取消引用。这样,在使用引用值时,您似乎直接使用目标对象。但中间总有一个参考,再跳到目标一步。

下面是证明 Python 使用引用传递的示例:

Illustrated example of passing the argument

如果参数是按值传递的,则无法修改外部参数。绿色是目标对象(黑色是存储在里面的值,红色是对象类型),黄色是内存,里面有参考值,画成箭头。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部词典。(它实际上也可以画成一个绿色的椭圆。颜色和形状只说它是内部的。lst

您可以使用 id() 内置函数来了解引用值是什么(即目标对象的地址)。

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

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

评论

45赞 cayhorstmann 12/20/2012
发明新的术语(例如“按引用值传递”或“按对象调用”)是没有帮助的。“Call by (value|reference|name)”是标准术语。“参考”是一个标准术语。使用标准术语按值传递引用可以准确地描述 Python、Java 和许多其他语言的行为。
6赞 pepr 12/20/2012
@cayhorstmann:问题在于 Python 变量的术语含义与其他语言不同。这样,通过引用调用就不适合这里了。另外,您如何准确定义术语引用?非正式地,Python 方式可以很容易地描述为传递对象的地址。但它不适合 Python 的潜在分布式实现。
1赞 Honest Abe 1/14/2014
我喜欢这个答案,但你可能会考虑这个例子是否真的有助于或损害流程。此外,如果将“引用值”替换为“对象引用”,则将使用我们可以认为是“官方”的术语,如下所示: 定义函数
1赞 pepr 1/15/2014
好吧,但是官员说......“参数是使用 Call By Value 传递的(其中始终是对象引用,而不是对象的值)。这样,您可能会想在文本上将其替换为......参数是使用调用引用传递的,这有点令人困惑,因为它不是真的。这种混淆是由一个更复杂的情况引起的,其中没有一个经典术语是完全合适的。我没有找到任何更简单的例子来说明这种行为。
2赞 Honest Abe 1/16/2014
在该引文的末尾有一个脚注,内容如下:“实际上,按对象引用调用将是一个更好的描述,因为如果传递了一个可变对象,调用者将看到被调用者对它所做的任何更改......” 我同意你的看法,混淆是由于试图适应其他语言建立的术语造成的。撇开语义不谈,需要了解的是:字典/命名空间、名称绑定操作以及名称→指针→对象的关系(如您所知)。
10赞 matino 10/2/2012 #9

这是对 Python 中使用的概念的简单(我希望)解释。
每当将对象传递给函数时,都会传递对象本身(Python 中的对象实际上是您在其他编程语言中所说的值),而不是对此对象的引用。换言之,当您调用:
pass by object

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)

这显然会改变对象。这个答案很好地解释了这一点。

评论

3赞 pepr 10/4/2012
问题是作业做的事情超出了您的预期。导致将名称重用于其他内容并忘记最初传递的对象。但是,您可以尝试(顺便说一句,变量的名称错误。思考完全是胡说八道。无论如何,你认为对象本身被传递意味着什么?您认为将什么复制到函数中?list = [1, 2, 3]listlist[:] = [1, 2, 3]list[0, 1] = [1, 2, 3]
0赞 Veky 5/9/2014
@pepr对象不是文字。它们是对象。谈论它们的唯一方法是给它们起一些名字。这就是为什么一旦你掌握了它,它就会如此简单,但解释起来却非常复杂。:-)
0赞 pepr 5/12/2014
@Veky:我知道这一点。无论如何,列表文本将转换为列表对象。实际上,Python 中的任何对象都可以在没有名称的情况下存在,即使没有给出任何名称也可以使用。你可以把它们看作是匿名对象。将对象视为列表的元素。他们不需要名字。您可以通过索引列表或循环访问列表来访问它们。无论如何,我坚持认为这只是一个坏例子。Python 中没有这样的东西。[0, 1] = [1, 2, 3]
0赞 Veky 5/12/2014
@pepr:我不一定是指 Python 定义名称,只是普通名称。当然算作 alist 的第三个元素的名称。但我想我误解了你的问题所在。:-)alist[2]
0赞 Veky 5/15/2014
哎呀。我的英语显然比我的 Python 差得多。:-)我会再试一次。我只是说你必须给对象一些名字才能谈论它们。我所说的“名称”并不是指“Python 定义的名称”。我知道 Python 机制,别担心。
86赞 Raymond Hettinger 3/29/2013 #10

Effbot(又名 Fredrik Lundh)将 Python 的可变传递风格描述为按对象调用:http://effbot.org/zone/call-by-object.htm

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

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

  • 当您使用 更新 “x” 时,将创建一个新的整数对象,并且字典将更新为指向新对象。旧的一千个对象是不变的(并且可能还活着,也可能不活着,这取决于是否有其他东西引用该对象)。x = 2000

  • 当您执行新赋值(如 )时,将创建一个新的字典条目“y”,该条目指向与“x”条目相同的对象。y = x

  • 字符串和整数等对象是不可变的。这仅仅意味着没有方法可以在创建对象后对其进行更改。例如,一旦创建了整数对象 100,它就永远不会改变。数学是通过创建新的整数对象来完成的。

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

希望这能为您澄清问题。

评论

6赞 Honest Abe 1/14/2014
我真的很感激从开发人员那里了解到这一点。该函数是否像 pepr 的答案所暗示的那样返回指针(对象引用)的值?id()
7赞 Raymond Hettinger 1/14/2014
@HonestAbe 是的,在 CPython 中,id() 返回地址。但在 PyPy 和 Jython 等其他 python 中,id() 只是一个唯一的对象标识符。
0赞 Karl Knechtel 7/13/2023
字典是一个合理的心智模型,对于 2.x 来说相当准确;但在 3.x 中,局部变量具有优化的存储,它不像字典那样工作,并且该函数需要动态创建一个。locals()dict
18赞 Nuno Aniceto 2/11/2014 #11

正如你所说,你需要有一个可变的对象,但我建议你检查一下全局变量,因为它们可以帮助你,甚至解决这种问题!

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
<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

评论

5赞 Max P Magee 7/1/2014
我很想发布类似的回答——最初的提问者可能不知道他想要的实际上是使用一个全局变量,在函数之间共享。这是我本来会分享的链接:stackoverflow.com/questions/423379/......作为对@Tim的回答,Stack Overflow 不仅仅是一个问答网站,它是一个巨大的参考知识库,只会变得更强大、更细致——就像一个活跃的 wiki——有更多的输入。
103赞 Lutz Prechelt 2/11/2014 #12

Python 中没有变量

理解参数传递的关键是停止思考“变量”。Python 中有名称和对象,它们一起 看起来像变量,但始终区分这三个变量很有用。

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

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

例:

a = 1

这会将名称绑定到保存值 1 的整数类型的对象。a

b = x

这会将名称绑定到该名称当前绑定到的同一对象。 之后,这个名字就和这个名字没有关系了。bxbx

请参阅 Python 3 语言参考中的第 3.14.2 节。

如何阅读问题中的示例

在问题中所示的代码中,该语句将名称(在函数范围内)绑定到保存值的对象,赋值(在函数主体中)再次将相同的名称分配给其他对象(恰好也包含字符串,但可能完全是其他对象)。self.Change(self.variable)varChange'Original'var = 'Changed'Change

如何通过引用传递

因此,如果您要更改的东西是可变对象,则没有问题,因为所有内容都是通过引用有效地传递的。

如果它是一个不可变的对象(例如布尔、数字、字符串),那么要走的方法是将其包装在一个可的对象中。
快速而肮脏的解决方案是一个单元素列表(而不是 ,pass 和 in the function modify )。
python 化的方法是引入一个普通的单属性类。该函数接收类的实例并操作该属性。
self.variable[self.variable]var[0]

评论

51赞 Ned Batchelder 6/24/2014
“Python 没有变量”是一个愚蠢而令人困惑的口号,我真的希望人们不要再说它了...... :(这个答案的其余部分很好!
23赞 Lutz Prechelt 6/25/2014
这可能令人震惊,但并不愚蠢。而且我也不认为这令人困惑:它希望能打开接受者对即将到来的解释的思路,并让她处于一种有用的“我想知道他们有什么而不是变量”的态度。(是的,您的里程可能会有所不同。
25赞 Ned Batchelder 6/25/2014
你还会说 Javascript 没有变量吗?它们的工作方式与 Python 相同。此外,Java、Ruby、PHP 等我认为更好的教学技巧是,“Python 的变量的工作方式与 C 变量不同。
13赞 Ned Batchelder 10/30/2014
是的,Java 有变量。Python 和 JavaScript、Ruby、PHP 等也是如此。在 Java 中,你不会说它声明了一个变量,但实际上没有。它们都声明变量。变量是对象,变量是基元。例如,您通过显示 来演示变量的工作原理。这在 Python 中也完全正确(因为 Python 中没有使用)!intIntegerIntegerinta = 1; b = a; a++ # doesn't modify b+= 1++
7赞 Lutz Prechelt 10/9/2015
“变量”的概念很复杂,而且往往很模糊:变量是值的容器,由名称标识。在 Python 中,值是对象,容器是对象(看到问题了吗?),名称实际上是独立的东西。我相信以这种方式准确理解变量要困难得多。名称和对象的解释看起来更困难,但实际上更简单。
459赞 Zenadix 9/5/2014 #13

我发现其他答案相当长且复杂,因此我创建了这个简单的图表来解释 Python 处理变量和参数的方式。enter image description here

评论

9赞 Martijn Pieters 5/26/2016
感谢您的更新,好多了!让大多数人感到困惑的是分配给订阅;例如,与直接赋值、.B[0] = 2B = 2
14赞 Hatshepsut 7/5/2016
“A 被分配给 B。”这不是模棱两可吗?我认为在普通英语中,这可能意味着要么 or .A=BB=A
0赞 Madivad 7/19/2016
我确实喜欢视觉表示,但仍然错过了 vs 的重点,这使得右腿没有意义,因为没有可用。(不过仍然得到了视觉代表的赞成票):)mutableimmutableappend
0赞 Abhinav 10/20/2019
B是就地修改是什么意思?B 不是对象
0赞 Mark Ransom 3/25/2021
@Abhinav在 Python 中,一切都是一个对象——甚至是简单的整数。但有些对象可以修改(可变),有些则不能。如果对象有方法,那么它必须是可变的。append
19赞 Joop 9/12/2014 #14

这里的答案有很多见解,但我认为这里没有明确提到另一点。引用 Python 文档 Python 中局部变量和全局变量的规则是什么?

在 Python 中,仅在函数中引用的变量是隐式全局变量。如果在函数主体内的任何位置为变量分配了新值,则假定该变量为局部变量。如果在函数中为变量分配了新值,则该变量是隐式局部变量,您需要将其显式声明为“全局”。 虽然一开始有点令人惊讶,但稍加考虑就解释了这一点。一方面,要求对分配的变量进行全局设置可以防止意外的副作用。另一方面,如果所有全局引用都需要 global,则将一直使用 global。您必须将对内置函数或导入模块的组件的每个引用声明为全局。这种混乱将破坏全球宣言在确定副作用方面的有用性。

即使将可变对象传递给函数,这仍然适用。对我来说,它清楚地解释了分配给对象和在函数中对对象进行操作之间行为差异的原因。

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

因此,对未声明为全局变量的赋值将创建一个新的局部对象,并断开与原始对象的链接。

22赞 ajknzhol 3/27/2015 #15

Python 的逐个传递方案与 C++ 的引用参数选项并不完全相同,但事实证明,它在实践中与 C 语言(和其他语言)的参数传递模型非常相似:

  • 不可变参数实际上是“按值”传递的。整数和字符串等对象是通过对象引用而不是通过复制传递的,但由于您无论如何都无法更改不可变对象,因此效果很像制作副本。
  • 可变参数实际上是“通过指针”传递的。列表等对象 而字典也是通过对象引用传递的,这与 C 的方式类似 将数组作为指针传递 - 可以在函数中就地更改可变对象, 很像 C 数组。
10赞 Dolf Andringa 2/8/2016 #16

除了所有关于这些东西在 Python 中如何工作的精彩解释之外,我没有看到针对该问题的简单建议。正如您似乎在创建对象和实例一样,处理实例变量和更改它们的 Python 方式如下:

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

    def Change(self):
        self.variable = 'Changed'

在实例方法中,通常引用访问实例属性。在实例方法中设置实例属性并在实例方法中读取或更改实例属性是正常的。这也是为什么你把第一个参数传递给 的原因。self__init__selfdef 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
9赞 itmuckel 4/22/2016 #17

若要模拟按引用传递对象,请将其包装在一个单项列表中:

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print(obj.name)

p = [obj]
changeRef(p)

print(p[0].name)

分配给列表的元素会改变列表,而不是重新分配名称。由于列表本身具有引用语义,因此更改将反映在调用方中。

评论

0赞 Minh Tran 10/20/2018
p是对可变列表对象的引用,该对象又存储该对象。引用 'p' 被传递到 .在内部 ,将创建一个新引用(称为新引用),该引用指向指向的同一列表对象。但是,由于列表是可变的,因此对列表的更改在两个引用中都可见。在本例中,您使用引用更改索引 0 处的对象,以便它随后存储该对象。对列表对象的更改是使用 完成的,但此更改对 可见。objchangeRefchangeRefrefprefPassByReference('Michael')refp
0赞 Minh Tran 10/20/2018
所以现在,引用 和 指向一个存储单个对象的列表对象。因此,返回 .当然,现在已经超出了范围,可能会被垃圾收集,但都一样。prefPassByReference('Michael')p[0].nameMichaelref
1赞 Minh Tran 10/20/2018
但是,您尚未更改与引用关联的原始对象的私有实例变量 。其实会回来的.上述评论假定给出的定义。namePassByReferenceobjobj.namePeterMark Ransom
1赞 Minh Tran 10/20/2018
关键是,我不同意这是一个黑客攻击(我的意思是指一些有效的东西,但出于未知、未经测试或实施者无意的原因)。您只需将列表中的一个对象替换为另一个对象,并引用两个对象中的后者即可。PassByReferencePassByReference
9赞 Brad Porter 8/9/2016 #18

我使用以下方法快速将一些 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

评论

0赞 kasimir 5/2/2020
是的,这也解决了我的用例中的“引用传递”。我有一个函数,基本上可以清理 a 中的值,然后返回 .但是,在清理时,可能需要重建系统的一部分。因此,该函数不仅必须返回清理后的数据,而且还必须能够发出重建信号。我试图通过引用传递一个,但ofc不起作用。弄清楚如何解决这个问题,我发现您的解决方案(基本上是返回一个元组)效果最好,同时根本不是黑客/解决方法(恕我直言)。dictdictdictbool
3赞 textshell 8/20/2016 #19

虽然通过引用传递并不适合 Python,并且应该很少使用,但有一些解决方法实际上可以将当前分配给局部变量的对象,甚至可以从被调用函数内部重新分配局部变量。

基本思想是有一个函数可以进行这种访问,并且可以作为对象传递给其他函数或存储在类中。

一种方法是在包装函数中使用(用于全局变量)或(用于函数中的局部变量)。globalnonlocal

def change(wrapper):
    wrapper(7)

x = 5

def setter(val):
    global x
    x = val

print(x)

同样的想法也适用于读取和计算变量。del

对于仅读取,甚至还有一种更短的方法来使用,它返回一个可调用对象,当调用时返回 x 的当前值。这有点像遥远过去语言中使用的“按名字称呼”。lambda: 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

Python 的“反射”支持使得可以获取能够在给定范围内重新分配名称/变量的对象,而无需在该范围内显式定义函数:

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)

在这里,该类包装了字典访问。因此,属性访问被转换为传递的字典中的项目访问。通过传递内置变量的结果和局部变量的名称,这最终会访问局部变量。从 3.5 开始的 Python 文档建议更改字典可能不起作用,但它似乎对我有用。ByRefwrappedlocals

评论

0赞 Peter Mortensen 2/19/2023
回复“3.5 版的 Python 文档”:你能添加参考吗?(但是 **** 没有 **** “编辑:”, “更新:”, 或类似 - 答案应该看起来好像是今天写的。
4赞 mARK bLOORE 10/31/2016 #20

鉴于 Python 处理值和对它们的引用的方式,引用任意实例属性的唯一方法是按名称:

class PassByReferenceIsh:
    def __init__(self):
        self.variable = 'Original'
        self.change('variable')
        print self.variable

    def change(self, var):
        self.__dict__[var] = 'Changed'

当然,在实际代码中,你会在字典查找时添加错误检查。

3赞 Jesse Hogan 9/10/2017 #21

由于您的示例恰好是面向对象的,因此您可以进行以下更改以获得类似的结果:

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'

评论

2赞 Bishwas Mishra 3/14/2018
虽然这有效。它不是通过引用传递的。它是“通过对象引用传递”。
3赞 sergzach 5/3/2018 #22

您只能使用空类作为实例来存储引用对象,因为内部对象属性存储在实例字典中。请参阅示例。

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)
2赞 Alok Garg 1/23/2019 #23

Python 中的引用传递与 C++/Java 中的引用传递概念完全不同。

  • Java 和 C#:基元类型(包括字符串)按值传递 (copy)。引用类型是通过引用(地址复制)传递的,因此调用方可以看到在被调用函数中的参数中所做的所有更改。

  • C++:允许按引用传递或按值传递。如果参数是通过引用传递的,则可以修改它,也可以不修改它,具体取决于该参数是否作为 const 传递。但是,无论是否常量,该参数都保持对对象的引用,并且不能将引用分配给指向被调用函数中的其他对象。

  • 蟒:Python 是“按对象引用传递”,其中经常有人说:“对象引用是按值传递的。(在这里阅读)。调用方和函数都引用同一个对象,但函数中的参数是一个新变量,它只是在调用方中保存对象的副本。与 C++ 一样,参数可以在函数中修改或不修改。这取决于传递的对象类型。例如,不可变对象类型不能在被调用的函数中修改,而可变对象可以更新或重新初始化。

    更新或重新分配/重新初始化可变变量之间的一个关键区别是,更新的值会反映回被调用的函数中,而重新初始化的值不会。将新对象赋值给可变变量的任何范围都是 python 中函数的本地范围。@blair-conrad 提供的例子非常适合理解这一点。

评论

2赞 John 9/12/2019
老了,但我觉得有义务纠正它。字符串在 Java 和 C# 中都是通过引用传递的,而不是通过值传递的
1赞 jjaskulowski 11/11/2021
不。在 c# 中,一切都按值传递。就是 c# 中作为对象的变量的值恰好是对象的堆 ID/地址。因此,当您将函数中的某些内容设置为新对象时,您将函数中的变量设置为地址。通过引用传递意味着将 adres 传递给值,对于结构类型,它是指向值的地址,但在对象的情况下是指向指针的地址。
4赞 Liakos 5/5/2019 #24

由于字典是通过引用传递的,因此可以使用 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!"
    return result

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 returned value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
6赞 Daniel Jour 5/10/2019 #25

由于似乎没有提到,因此模拟引用的方法,例如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

这对于“仅输出引用”或具有多个线程/进程的情况(通过使更新函数线程/多处理安全)最有用。

显然,上面不允许读取值,只能更新它。

评论

0赞 Karl Knechtel 7/13/2023
我认为最好有一个单独的、与语言无关的问答,用于模拟传递引用的策略。
1赞 Magnus 7/19/2020 #26

我是 Python 的新手,昨天开始(尽管我已经编程了 45 年)。

我来这里是因为我正在编写一个函数,我想有两个所谓的输出参数。如果它只是一个输出参数,我现在就不会挂断检查引用/值在 Python 中的工作方式。我只会使用函数的返回值。但是由于我需要两个这样的参数,我觉得我需要整理一下。

在这篇文章中,我将展示我是如何解决我的问题的。也许来到这里的其他人会发现它很有价值,即使它并不完全是主题问题的答案。有经验的 Python 程序员当然已经知道我使用的解决方案,但这对我来说是新的。

从这里的答案中,我可以很快看到 Python 在这方面的工作方式有点像 JavaScript,如果您想要参考功能,则需要使用解决方法。

但后来我在 Python 中发现了一些我认为我以前在其他语言中从未见过的巧妙之处,即你可以以简单的逗号分隔方式从一个函数返回多个值,如下所示:

def somefunction(p):
    a = p + 1
    b = p + 2
    c = -p
    return a, b, c

并且您可以在调用端进行类似的处理,如下所示

x, y, z = somefunction(w)

这对我来说已经足够好了,我很满意。无需使用某些解决方法。

在其他语言中,你当然也可以返回许多值,但通常在一个对象的 from 中,你需要相应地调整调用端。

Python 的执行方法既好又简单。

如果你想通过引用进行更多的模仿,你可以这样做:

def somefunction(a, b, c):
    a = a * 2
    b = b + a
    c = a * b * c
    return a, b, c

x = 3
y = 5
z = 10
print(F"Before : {x}, {y}, {z}")

x, y, z = somefunction(x, y, z)

print(F"After  : {x}, {y}, {z}")

这给出了这个结果

Before : 3, 5, 10
After  : 6, 11, 660

评论

4赞 juanpa.arrivillaga 8/13/2020
“但后来我在 Python 中发现了一些我认为我以前在其他语言中没有见过的东西,即你可以从一个函数返回多个值”不,你不能。您正在做的是返回一个值 a ,这是表达式创建的值。然后,使用可迭代的解包将该元组解压缩为单独的变量。当然,实际上,你可以把它看作是“返回多个值”,但你实际上并没有这样做,你是在返回一个容器。tuplea, b, c
2赞 Magnus 8/13/2020
@juanpa.arrivillaga,是的,当我写下答案时,我意识到了这一点,我刚刚读过它。但我只是以一种实用的方式描述了整个事情,而没有详细介绍它是如何工作的,并为我的答案增加了不必要的长度。你确实可以从一个函数中返回多个值,如果它是在对象或类似函数中完成的,比如在元组中(在 Python 中以我展示的简洁方式处理)。当我从一家公司订购东西时,他们可以给我寄很多东西,即使它们都装在一个包裹里。
0赞 Karl Knechtel 7/13/2023
这漫无边际,而且大多偏离了 OP 问题的主题。相反,它指的是这个概念:stackoverflow.com/questions/354883 - 其他现有的问答很好地解决了这个问题。
1赞 Julian wandhoven 1/28/2021 #27

或者,您可以使用如下所示的 ctypes

import ctypes

def f(a):
    a.value = 2398 ## Resign the value in a function

a = ctypes.c_int(0)
print("pre f", a)
f(a)
print("post f", a)

因为 a 是 c int 而不是 Python 整数,显然是通过引用传递的。但是,您必须小心,因为可能会发生奇怪的事情,因此不建议这样做。

0赞 Jop Knoppers 3/26/2021 #28

这很可能不是最可靠的方法,但这是有效的,请记住,您正在重载内置的 str 函数,这通常是您不想做的事情:

import builtins

class sstr(str):
    def __str__(self):
        if hasattr(self, 'changed'):
            return self.changed

        return self

    def change(self, value):
        self.changed = value

builtins.str = sstr

def change_the_value(val):
    val.change('After')

val = str('Before')
print (val)
change_the_value(val)
print (val)
1赞 Stepan Dyatkovskiy 6/29/2021 #29

使用数据类。此外,它还允许您应用类型限制(又称“类型提示”)。

from dataclasses import dataclass

@dataclass
class Holder:
    obj: your_type # Need any type? Use "obj: object" then.

def foo(ref: Holder):
    ref.obj = do_something()

我同意人们的观点,在大多数情况下,您最好考虑不要使用它。

然而,当我们谈论上下文时,以这种方式了解是值得的。

不过,您可以设计一个显式的上下文类。在制作原型时,我更喜欢数据类,因为它很容易来回序列化它们。

评论

0赞 Wolfgang Fahl 11/28/2022
1.9 Mill 视图,13 年后出现了一个不错的解决方案。我已经实现了它,将 Holder 称为“State”并添加了一个布尔值,现在可以在不同的函数中修改该值......很棒,很干净!
0赞 Karl Knechtel 7/13/2023
@WolfgangFahl前面的几个答案在功能上都做同样的事情,只是使用列表元素、普通对象属性等而不是 DataClass 实例属性。它们从根本上都以相同的方式工作:由于按值传递具有引用语义的对象允许突变但不允许重新分配,因此我们通过创建包装器对象将所需的重新分配转换为突变。
0赞 Stepan Dyatkovskiy 7/16/2023
完全同意 @Karl Knechtel。我强调这种情况的唯一原因是数据类支持对序列化非常友好,并且提供了最少的混淆 API。因此,如果有人要扩展您的代码,那么您就不会试图对您应该用作引用持有者的内容执行列表操作。
1赞 S.B 10/4/2022 #30

关于这个问题已经有很多很好的答案(或者让我们说意见),我已经读过了,但我想提一个缺失的答案。来自 Python 文档的 FAQ 部分。我不知道发布此页面的日期,但这应该是我们真正的参考:

请记住,参数是在 Python 中通过赋值传递的。因为 Assignment 只是创建对对象的引用,没有别名 在调用方和被调用方的参数名称之间,等等 按引用调用本身。

如果您有:

a = SOMETHING

def fn(arg):
    pass

你称它为,你正在做你在任务中所做的。所以这会发生:fn(a)

arg = a

将创建对 的附加引用。变量只是符号/名称/引用。他们不“持有”任何东西。SOMETHING