如何通过引用传递变量?

参数是通过引用传递还是通过值传递?如何引用传递以使下面的代码输出'Changed'而不是'Original'

class PassByReference:def __init__(self):self.variable = 'Original'self.change(self.variable)print(self.variable)
def change(self, var):var = 'Changed'
1863672 次浏览

在这种情况下,方法Change中名为var的变量被分配了一个对self.variable的引用,您立即将一个字符串分配给var。它不再指向self.variable。以下代码片段显示了如果您修改varself.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']>>>

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

争论是0。这背后的理由是双重的:

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

由于传入的参数是对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指向的位置。

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 agogot It was many and many a year agoset to In a kingdom by the seaafter, outer_string = It was many and many a year ago

同样,由于the_string参数是按值传递的,因此为其分配一个新字符串对方法外部的代码没有影响。the_stringouter_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 likemy_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 likewrapper = [my_string]use_a_wrapper_to_simulate_pass_by_reference(wrapper)
do_something_with(wrapper[0])

虽然这看起来有点麻烦。

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

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

想象一下传递通过分配而不是通过引用/值传递的东西。这样,只要你理解正常赋值过程中发生的事情,它总是很清楚。

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

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

由于不可变类型不能修改,它们似乎就像按值传递一样——将int传递给函数意味着将int分配给函数的参数。你只能重新分配,但它不会改变原始变量的值。

它既不是按值传递也不是按引用传递——它是按对象调用的。看这个,Fredrik Lundh:

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

以下是一个重要的引用:

“……变量[名称]是没有对象;它们不能由其他变量表示或由对象引用。”

在你的例子中,当调用Change方法时——为它创建了一个命名空间var成为该命名空间内字符串对象'Original'的名称。然后,该对象在两个命名空间中有一个名称。接下来,var = 'Changed'var绑定到一个新的字符串对象,因此该方法的命名空间忘记了'Original'。最后,该命名空间被遗忘,字符串'Changed'也随之被遗忘。

(编辑-布莱尔已经更新了他非常受欢迎的答案,现在它是准确的)

我认为重要的是要注意,当前投票最多的帖子(Blair Conrad)虽然在结果上是正确的,但它具有误导性,并且根据其定义是不正确的。虽然有许多语言(如C)允许用户引用传递或值传递,但Python不是其中之一。

David Cournapeau的回答指向了真正的答案,并解释了为什么Blair Conrad帖子中的行为似乎是正确的,而定义却不是。

在Python是值传递的情况下,所有语言都是值传递的,因为必须发送一些数据(无论是“值”还是“引用”)。然而,这并不意味着Python是C程序员所认为的值传递。

如果你想要这种行为,Blair Conrad的答案很好。但是如果你想知道为什么Python既不是值传递也不是引用传递的具体细节,请阅读David Cournapeau的答案。

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

def Change(self, var):var[0] = 'Changed'
variable = ['Original']self.Change(variable)print variable[0]

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

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

a = 1a = 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总是使用引用传递值。我要重复我的另一个答案来支持我的陈述。

Python总是使用按引用传递的值。没有任何例外。任何变量赋值都意味着复制引用值。没有例外。任何变量都是与引用值绑定的名称。总是。

您可以将引用值视为目标对象的地址。使用时地址会自动取消引用。这样,使用引用值,似乎您直接使用目标对象。但两者之间总是有一个引用,更容易跳到目标。

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

传递参数的示例

如果参数是按值传递的,则无法修改外部的lst。绿色是目标对象(黑色是存储在内部的值,红色是对象类型),黄色是内部有引用值的内存——绘制为箭头。蓝色实心箭头是传递给函数的引用值(通过虚线蓝色箭头路径)。难看的深黄色是内部字典。(它实际上也可以绘制为绿色椭圆。颜色和形状只是说它是内部的。)

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

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

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

这是对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)

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

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

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

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

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

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

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

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

希望这能为你澄清这个问题。

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

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>>>> yTraceback (most recent call last):File "<stdin>", line 1, in <module>NameError: name 'y' is not defined>>> zTraceback (most recent call last):File "<stdin>", line 1, in <module>NameError: name 'z' is not defined
>>> x(2)>>> x<function x at 0x00000000020E1730>>>> yTraceback (most recent call last):File "<stdin>", line 1, in <module>NameError: name 'y' is not defined>>> z2

Python中没有变量

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

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

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

示例:

a = 1

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

b = x

这将名称b绑定到名称x当前绑定到的同一个对象。之后,名称b与名称x不再有任何关系。

请参阅Python 3语言参考中的3.14.2部分。

如何阅读问题中的示例

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

如何引用传递

所以如果你想要改变的是一个可变的对象,那就没有问题了,因为一切都是通过引用有效传递的。

如果它是一个不可变对象(例如布尔、数字、字符串),那么要做的就是将其包装在一个可变对象中。
这个快速而脏的解决方案是一个单元素列表(而不是self.variable,传递[self.variable]并在函数中修改var[0])。
pythonic的方法是引入一个简单的单属性类。该函数接收该类的一个实例并操作该属性。

我发现其他答案相当长且复杂,所以我创建了这个简单的图表来解释Python处理变量和参数的方式。输入图片描述

这里的答案有很多见解,但我认为这里没有明确提到另外一点。引用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] 4454645632Received [1, 2, 3] 4454645632Changed to [0, 0, 0] 4474591928After [1, 2, 3] 4454645632

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

Python的赋值传递方案与C++的引用参数选项不太一样,但事实证明它与C语言(和其他语言)的参数传递模型非常相似:

  • 不可变参数被有效地传递“按价值”。整数和字符串等对象通过对象引用而不是复制传递,但是因为无论如何都无法更改不可变对象,所以效果很像复制。
  • 可变参数被有效地传递“通过指针”。对象,例如列表和字典也通过对象引用传递,这类似于C的方式将数组作为指针传递-可变对象可以在函数中就地更改,很像C数组。

除了关于这些东西在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
@staticmethoddef 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

这是一个丑陋的黑客,但它有效。

我使用下面的方法快速地将几个Fortran代码转换为Python。诚然,它并不像最初提出的问题那样引用传递,但在某些情况下是一个简单的解决方法。

a=0b=0c=0def myfunc(a,b,c):a=1b=2c=3return a,b,c
a,b,c = myfunc(a,b,c)print a,b,c

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

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

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

def change(wrapper):wrapper(7)
x = 5def setter(val):global xx = valprint(x)

同样的想法适用于读取和del设置变量。

对于只读,甚至有一种更短的方法,就是使用lambda: x返回一个可调用的值,当调用时返回x的当前值。这有点像遥远的过去语言中使用的“按名称调用”。

传递3个包装器来访问一个变量有点笨拙,所以可以将它们包装到一个具有代理属性的类中:

class ByRef:def __init__(self, r, w, d):self._read = rself._write = wself._delete = ddef 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 / nonlocalr = ByRef(get, set, remove)r.wrapped = 15

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

class ByRef:def __init__(self, locs, name):self._locs = locsself._name = namedef set(self, val):self._locs[self._name] = valdef 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 = 6print(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'

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

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

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'

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

class RefsObj(object):"A class which helps to create references to variables."pass
...
# an example of usagedef change_ref_var(ref_obj):ref_obj.val = 24
ref_obj = RefsObj()ref_obj.val = 1print(ref_obj.val) # or print ref_obj.val for python2change_ref_var(ref_obj)print(ref_obj.val)

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

  • Java&C#:基元类型(包括字符串)值传递(复制),引用类型通过引用(地址复制)传递,因此调用者可以看到调用函数中参数的所有更改。
  • C++:通过引用传递或通过值传递都是允许的。如果参数是通过引用传递的,你可以根据参数是否作为const传递来修改它。但是,无论是否const,参数都维护对对象的引用,并且不能将引用分配给指向被调用函数中的不同对象。
  • Python:[阅读这里]1。调用者和函数都引用同一个对象,但是函数中的参数是一个新的变量,它只是在调用者中保存了对象的副本。像C++一样,参数在函数中可以被修改也可以不被修改-这取决于传递的对象的类型。更新或重新分配/重新初始化可变变量之间的一个关键区别是更新后的值会反映回被调用的函数中,而重新初始化的值不会。将新对象分配给可变变量的范围是python中函数的本地范围。@blair-conrad提供的示例非常适合理解这一点。

由于字典是通过引用传递的,因此您可以使用字典变量将任何引用的值存储在其中。

# returns the result of adding numbers `a` and `b`def AddNumbers(a, b, ref): # using a dict for referenceresult = a + bref['multi'] = a * b # reference the multi. ref['multi'] is numberref['msg'] = "The result: " + str(result) + " was nice!"return result
number1 = 5number2 = 10ref = {} # 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 valueprint("multi: ", ref['multi'])  # a referenced valueprint("msg: ", ref['msg'])      # a referenced value

由于似乎没有提到模拟引用的方法,例如C++是使用“更新”函数并传递它而不是实际变量(或者更确切地说,“名称”):

def need_to_modify(update):update(42) # set new value 42# other code
def call_it():value = 21def update_value(new_value):nonlocal valuevalue = new_valueneed_to_modify(update_value)print(value) # prints 42

这对于“只限引用”或在多线程/进程的情况下(通过使更新函数线程/多重处理安全)非常有用。

显然上面不允许阅读的值,只更新它。

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

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

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

从这里的答案中,我可以很快看到Python在这方面的工作方式有点像Javascript,如果您想要引用功能,您需要使用变通方法。

但是后来我在Python中发现了一些我认为我以前在其他语言中没有见过的整洁的东西,即您可以以简单的逗号分隔方式从函数中返回多个值,如下所示:

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

你可以在呼叫端类似地处理它,像这样

x, y, z = somefunction(w)

这对我来说已经足够了,我很满意。不需要使用一些变通方法。

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

Python的方式很好也很简单。

如果你想进一步模仿引用,你可以这样做:

def somefunction(a, b, c):a = a * 2b = b + ac = a * b * creturn a, b, c
x = 3y = 5z = 10print(F"Before : {x}, {y}, {z}")
x, y, z = somefunction(x, y, z)
print(F"After  : {x}, {y}, {z}")

从而得出这个结果

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

或者你可以使用ctype女巫看起来像这样

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)

as a是一个c int而不是一个python整数,并通过引用进行传递。但是,您必须小心,因为可能会发生奇怪的事情,因此不建议

很可能不是最可靠的方法,但这是有效的,请记住,您正在重载内置的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)

这可能是一个优雅的面向对象的解决方案,但在Python中没有这个功能。一个更优雅的解决方案是让你从中创建子类的任何类。或者你可以将其命名为“MasterClass”。但是不是拥有单个变量和单个布尔值,而是使它们成为某种集合。我修改了实例变量的命名以符合PEP 8。

class PassByReference:def __init__(self, variable, pass_by_reference=True):self._variable_original = 'Original'self._variable = variableself._pass_by_reference = pass_by_reference # False => pass_by_valueself.change(self.variable)print(self)
def __str__(self):print(self.get_variable())
def get_variable(self):if pass_by_reference == True:return self._variableelse:return self._variable_original
def set_variable(self, something):self._variable = something
def change(self, var):self.set_variable(var)
def caller_method():
pbr = PassByReference(variable='Changed') # this will print 'Changed'variable = pbr.get_variable() # this will assign value 'Changed'
pbr2 = PassByReference(variable='Changed', pass_by_reference=False) # this will print 'Original'variable2 = pbr2.get_variable() # this will assign value 'Original'    

我解决了一个类似的需求如下:

要实现更改变量的成员函数,请不要传递变量本身,而是传递一个functools.partial,其中包含引用变量的setattr。在change()中调用functools.partial将执行settatr并更改实际引用的变量。

请注意,setattr需要变量的名称作为字符串。

class PassByReference(object):def __init__(self):self.variable = "Original"print(self.variable)self.change(partial(setattr,self,"variable"))print(self.variable)
def change(self, setter):setter("Changed")

数据类呢?此外,它允许您应用类型限制(又名“类型提示”)。

from dataclasses import dataclass
@dataclassclass Holder:obj: your_type # Need any type? Use "obj: object" then.
def foo(ref: Holder):ref.obj = do_something()

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

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

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

干杯!

大多数时候,通过引用传递的变量是类成员。我建议的解决方案是使用装饰器来添加可变字段和相应的属性。该字段是变量周围的类包装器。

@refproperty添加了self._myvar(可变)和self.myvar属性。

@refproperty('myvar')class T():pass
def f(x):x.value=6
y=T()y.myvar=3f(y._myvar)print(y.myvar)

它将打印6。

将此与:

class X:pass
x=X()x.myvar=4
def f(y):y=6
f(x.myvar)print(x.myvar)

在这种情况下,它不会工作。它将打印4。

代码如下:

def refproperty(var,value=None):def getp(self):return getattr(self,'_'+var).get(self)
def setp(self,v):return getattr(self,'_'+var).set(self,v)
def decorator(klass):orginit=klass.__init__setattr(klass,var,property(getp,setp))
def newinit(self,*args,**kw):rv=RefVar(value)setattr(self,'_'+var,rv)orginit(self,*args,**kw)
klass.__init__=newinitreturn klassreturn decorator
class RefVar(object):def __init__(self, value=None):self.value = valuedef get(self,*args):return self.valuedef set(self,main, value):self.value = value

简单答案:

在像c++这样的python中,当你创建一个对象实例并将其作为参数传递时,不会复制实例本身,因此你从函数外部和内部引用同一个实例,并且能够修改同一个对象实例的组件数据,因此更改对外部可见。

对于基本类型,python和c++的行为也彼此相同,因为现在创建了实例的副本,因此外部看到/修改的实例与函数内部不同。因此,从内部进行的更改在外部不可见。

下面是python和c++的真正区别:

c++有地址指针的概念,c++允许您传递指针,它绕过了基本类型的复制,因此函数内部可以影响与外部相同的实例,因此更改也对外部可见。这在python中没有等效的,所以如果没有变通方法(例如创建包装器类型)是不可能的。

这样的指针在python中可能很有用,但它不像在c++中那么必要,因为在c++中,你只能返回一个实体,而在python中你可以返回多个由逗号分隔的值(即元组)。所以在python中,如果你有变量a、b和c,并且希望一个函数持久地修改它们(相对于外部),你会这样做:

a=4b=3c=8
a,b,c=somefunc(a,b,c)# a,b,c now have different values here

这样的语法在c++中是不容易实现的,因此在c++你会这样做:

int a=4int b=3int c=8somefunc(&a,&b,&c)// a,b,c now have different values here
  1. Python为每个对象分配一个唯一的标识符,可以使用Python的内置id()函数找到该标识符。它可以验证函数调用中的实际参数和形式参数是否具有相同的id值,这表明虚拟参数和实际参数引用相同的对象。

  2. 请注意,实际参数和相应的虚拟参数是指同一个对象的两个名称。如果您将虚拟参数重新绑定到函数范围内的新值/对象,这不会影响实际参数仍然指向原始对象的事实,因为实际参数和虚拟参数是两个名称。

  3. 以上两个事实可以概括为“参数通过赋值传递”。即,

dummy_argument = actual_argument

如果将dummy_argument重新绑定到函数体中的新对象,actual_argument仍然引用原始对象。如果使用dummy_argument[0] = some_thing,那么这也会修改actual_argument[0]。因此可以通过修改传入的对象引用的组件/属性来达到“引用传递”的效果。当然,这需要传递的对象是可变对象。

  1. 为了与其他语言进行比较,您可以说Python以与C相同的方式按值传递参数,其中当您传递“通过引用”时,您实际上是按值传递引用(即指针)
def i_my_wstring_length(wstring_input:str = "", i_length:int = 0) -> int:i_length[0] = len(wstring_input)return 0
wstring_test  = "Test message with 32 characters."i_length_test = [0]i_my_wstring_length(wstring_test, i_length_test)print("The string:\n\"{}\"\ncontains {} character(s).".format(wstring_test, *i_length_test))input("\nPress ENTER key to continue . . . ")

我分享了另一种有趣的方式,让人们通过一个方便的工具来理解这个话题-可视化Python代码执行基于从@Mark Ransom传递可变列表的例子

只是玩它,然后你会弄清楚的。

  1. 传递一个字符串

输入图片描述

  1. 传递列表

输入图片描述

它可以通过[]

dta="15252"body=[dta]

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

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

如果您有:

a = SOMETHING
def fn(arg):pass

你把它叫做fn(a),你正在做你在作业中做的事情。所以发生了这样的事情:

arg = a

创建了对SOMETHING的附加引用。变量只是符号/名称/引用。它们不“保存”任何东西。