参数是按引用传递还是按值传递?如何通过引用传递,以便下面的代码输出 'Changed'
而不是 'Original'
?
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.change(self.variable)
print(self.variable)
def change(self, var):
var = 'Changed'
参数是 passed by assignment。这背后的理由是双重的:
传入的参数实际上是对对象的引用(但引用是按值传递的)有些数据类型是可变的,但有些不是
所以:
如果你将一个可变对象传递给一个方法,该方法会获得对同一个对象的引用,你可以随心所欲地改变它,但是如果你在方法中重新绑定引用,外部作用域将一无所知,并且之后大功告成,外部引用仍将指向原始对象。
如果将不可变对象传递给方法,仍然无法重新绑定外部引用,甚至无法改变对象。
为了更清楚,让我们举一些例子。
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
的引用,而不是它的副本,因此我们可以使用 mutating 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
指向的位置。
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_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'
id
给出了引用对象的标识,而不是引用本身。
https://i.stack.imgur.com/FdaCu.png
B[0] = 2
,与直接分配,B = 2
。
A=B
或 B=A
。
它既不是按值传递也不是按引用传递 - 它是按对象调用。看这个,Fredrik Lundh:
http://effbot.org/zone/call-by-object.htm
这是一个重要的报价:
“...变量 [名称] 不是对象;它们不能由其他变量表示或由对象引用。”
在您的示例中,当调用 Change
方法时,会为其创建一个 namespace; var
成为该名称空间内字符串对象 'Original'
的名称。然后该对象在两个命名空间中有一个名称。接下来,var = 'Changed'
将 var
绑定到一个新的字符串对象,因此该方法的命名空间忘记了 'Original'
。最后,这个命名空间被遗忘了,字符串 'Changed'
也被遗忘了。
swap
函数附加 Python 代码,如下所示:a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
想想通过赋值而不是通过引用/按值传递的东西。这样一来,只要您了解正常分配期间发生的事情,就会很清楚发生了什么。
因此,当将列表传递给函数/方法时,会将列表分配给参数名称。附加到列表将导致列表被修改。重新分配函数内的列表不会更改原始列表,因为:
a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b # prints [1, 2, 3, 4] ['a', 'b']
由于无法修改不可变类型,它们似乎是按值传递的——将 int 传递给函数意味着将 int 分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。
Python中没有变量
理解参数传递的关键是停止思考“变量”。 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
) 再次将相同的名称分配给某个其他对象(它恰好也包含一个字符串,但可能完全是其他东西)。
如何通过引用传递
因此,如果您要更改的是可变对象,则没有问题,因为所有内容都通过引用有效地传递。
如果它是一个 immutable 对象(例如,布尔值、数字、字符串),则可以将其包装在一个可变对象中。
快速而简单的解决方案是单元素列表 (代替 self.variable
,传递 [self.variable]
并在函数中修改 var[0]
)。
更多的 pythonic 方法是引入一个微不足道的单属性类。该函数接收该类的一个实例并操作该属性。
int
声明了一个变量,但 Integer
没有。他们都声明了变量。 Integer
变量是一个对象,int
变量是一个原语。例如,您通过显示 a = 1; b = a; a++ # doesn't modify b
演示了变量的工作方式。在 Python 中也是如此(使用 += 1
,因为 Python 中没有 ++
)!
Effbot(又名 Fredrik Lundh)将 Python 的变量传递风格描述为按对象调用:http://effbot.org/zone/call-by-object.htm
对象在堆上分配,指向它们的指针可以在任何地方传递。
当您进行诸如 x = 1000 之类的赋值时,将创建一个字典条目,将当前命名空间中的字符串“x”映射到指向包含一千的整数对象的指针。
当您使用 x = 2000 更新“x”时,将创建一个新的整数对象,并且更新字典以指向新对象。旧的 1000 个对象没有改变(并且可能存在也可能不存在,具体取决于是否有其他对象引用该对象)。
当您执行新的赋值(例如 y = x)时,会创建一个新的字典条目“y”,它指向与“x”条目相同的对象。
字符串和整数等对象是不可变的。这仅仅意味着没有方法可以在创建对象后更改它。例如,一旦创建了整数对象一千,它就永远不会改变。数学是通过创建新的整数对象来完成的。
像列表这样的对象是可变的。这意味着对象的内容可以被任何指向该对象的东西改变。例如,x = []; y = x; x.append(10); print y 将打印 [10]。空列表已创建。 “x”和“y”都指向同一个列表。 append 方法改变(更新)列表对象(如向数据库中添加记录),结果对“x”和“y”都可见(就像数据库更新对该数据库的每个连接都是可见的)。
希望能为您澄清问题。
id()
函数是否返回指针的(对象引用的)值?
从技术上讲,Python 总是使用传递引用值。我将重复 my other answer 以支持我的陈述。
Python 总是使用传递引用值。没有任何例外。任何变量赋值都意味着复制参考值。没有例外。任何变量都是绑定到引用值的名称。总是。
您可以将引用值视为目标对象的地址。该地址在使用时会自动取消引用。这样,使用参考值,您似乎直接使用目标对象。但中间总有一个参考,多一步跳转到目标。
这是证明 Python 使用按引用传递的示例:
https://i.stack.imgur.com/uzXcP.png
如果参数是按值传递的,则无法修改外部 lst
。绿色是目标对象(黑色是里面存储的值,红色是对象类型),黄色是里面有引用值的内存——如箭头所示。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部字典。 (它实际上也可以画成一个绿色的椭圆。颜色和形状只是说它是内部的。)
您可以使用 id()
内置函数来了解引用值是什么(即目标对象的地址)。
在编译语言中,变量是能够捕获类型值的内存空间。在 Python 中,变量是绑定到引用变量的名称(内部捕获为字符串),该变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。
参考值隐藏在 Python 中。没有任何明确的用户类型来存储参考值。但是,您可以使用列表元素(或任何其他合适容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上并不包含在容器中——只有对元素的引用才是。
我通常使用的一个简单技巧是将其包装在一个列表中:
def Change(self, var):
var[0] = 'Changed'
variable = ['Original']
self.Change(variable)
print variable[0]
(是的,我知道这可能很不方便,但有时这样做很简单。)
(编辑 - 布莱尔更新了他广受欢迎的答案,现在它是准确的)
我认为重要的是要注意当前投票最多的帖子(由布莱尔康拉德撰写),虽然就其结果而言是正确的,但它具有误导性,并且根据其定义是不正确的。虽然有许多语言(如 C)允许用户通过引用传递或按值传递,但 Python 不是其中之一。
David Cournapeau 的回答指向了真正的答案,并解释了为什么 Blair Conrad 的帖子中的行为似乎是正确的,而定义却不是。
在 Python 是按值传递的范围内,所有语言都是按值传递的,因为必须发送一些数据(无论是“值”还是“引用”)。但是,这并不意味着 Python 在 C 程序员所认为的意义上是按值传递的。
如果你想要这种行为,布莱尔康拉德的回答很好。但是,如果您想了解 Python 既不是按值传递也不是按引用传递的具体细节,请阅读 David Cournapeau 的回答。
void swap(int& x, int& y) { int temp = x; x = y; y = temp; }
将交换传递给它的变量。在 Pascal 中,您使用 var
而不是 &
。
你在这里得到了一些非常好的答案。
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' )
Python 的传递赋值方案与 C++ 的引用参数选项并不完全相同,但实际上它与 C 语言(和其他语言)的参数传递模型非常相似:
不可变参数有效地“按值”传递。整数和字符串等对象是通过对象引用而不是通过复制来传递的,但是由于无论如何您都无法更改不可变对象,因此效果很像复制。
可变参数有效地“通过指针”传递。列表和字典等对象也通过对象引用传递,这类似于 C 将数组作为指针传递的方式——可变对象可以在函数中就地更改,就像 C 数组一样。
在这种情况下,方法 Change
中标题为 var
的变量被分配了对 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 文档 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
因此,对未声明为 global 的全局变量的赋值会创建一个新的局部对象并断开与原始对象的链接。
正如您所说,您需要一个可变对象,但我建议您检查全局变量,因为它们可以帮助您甚至解决此类问题!
例子:
>>> 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
这是 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)
这显然会改变对象。 This answer 很好地解释了这一点。
list = [1, 2, 3]
导致将 list
名称重用于其他内容并忘记最初传递的对象。但是,您可以尝试 list[:] = [1, 2, 3]
(顺便说一下,list
是变量的错误名称。考虑 [0, 1] = [1, 2, 3]
完全是胡说八道。无论如何,您认为 对象本身已传递? 在你看来,什么是复制到函数中的?
[0, 1] = [1, 2, 3]
只是一个不好的例子。 Python 中没有类似的东西。
alist[2]
算作 alist 的第三个元素的名称。但我想我误解了你的问题。 :-)
除了关于这些东西如何在 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
我使用以下方法快速将几个 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
dict
中的值,然后返回 dict
。然而,在清理过程中,很明显需要重建系统的一部分。因此,该函数不仅必须返回已清理的 dict
,而且还必须能够发出重建信号。我试图通过引用传递 bool
,但 ofc 不起作用。弄清楚如何解决这个问题,我发现您的解决方案(基本上返回一个元组)效果最好,同时也根本不是黑客/解决方法(恕我直言)。
通过引用传递对象有一个小技巧,即使语言无法实现。它也适用于 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
p
是对可变列表对象的引用,该对象又存储对象 obj
。引用“p”被传递到 changeRef
。在 changeRef
中,创建了一个新引用(新引用称为 ref
),它指向 p
所指向的同一个列表对象。但是因为列表是可变的,所以对列表的更改对于 both 引用都是可见的。在本例中,您使用 ref
引用来更改索引 0 处的对象,以便它随后存储 PassByReference('Michael')
对象。对列表对象的更改是使用 ref
完成的,但此更改对 p
可见。
p
和 ref
指向存储单个对象 PassByReference('Michael')
的列表对象。因此,p[0].name
返回 Michael
。当然,ref
现在已经超出范围,可能会被垃圾回收,但都是一样的。
obj
关联的原始 PassByReference
对象的私有实例变量 name
。事实上,obj.name
将返回 Peter
。上述评论假定定义 Mark Ransom
给出。
PassByReference
对象替换为另一个 PassByReference
对象,并引用这两个对象中的后者。
鉴于 python 处理值和对它们的引用的方式,您可以引用任意实例属性的唯一方法是通过名称:
class PassByReferenceIsh:
def __init__(self):
self.variable = 'Original'
self.change('variable')
print self.variable
def change(self, var):
self.__dict__[var] = 'Changed'
当然,在实际代码中,您会在 dict 查找中添加错误检查。
由于您的示例恰好是面向对象的,因此您可以进行以下更改以实现类似的结果:
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'
由于似乎没有提到模拟引用的方法,例如 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
这对于“仅输出引用”或具有多个线程/进程的情况(通过使更新函数线程/多处理安全)非常有用。
显然上面不允许读取值,只能更新它。
由于字典是通过引用传递的,因此您可以使用 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
虽然通过引用传递并不适合python并且应该很少使用,但实际上有一些变通方法可以将当前分配给局部变量的对象,甚至从被调用函数内部重新分配局部变量。
基本思想是拥有一个可以进行访问的函数,并且可以作为对象传递给其他函数或存储在一个类中。
一种方法是在包装函数中使用 global
(用于全局变量)或 nonlocal
(用于函数中的局部变量)。
def change(wrapper):
wrapper(7)
x = 5
def setter(val):
global x
x = val
print(x)
同样的想法也适用于读取和del
变量。
仅仅为了阅读,还有一个更短的方法是使用 lambda: x
,它返回一个可调用的,当被调用时返回 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)
这里的 ByRef
类包装了一个字典访问。因此,对 wrapped
的属性访问被转换为传递字典中的项目访问。通过传递内置 locals
的结果和局部变量的名称,这最终访问了一个局部变量。从 3.5 开始的 python 文档建议更改字典可能不起作用,但它似乎对我有用。
您只能使用空类作为实例来存储引用对象,因为内部对象属性存储在实例字典中。请参阅示例。
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)
Python 中的引用传递与 C++/Java 中的引用传递概念完全不同。
Java&C#:原始类型(包括字符串)按值传递(副本),引用类型按引用传递(地址副本),因此调用函数中对参数所做的所有更改对调用者都是可见的。
C++:允许按引用传递或按值传递。如果参数是通过引用传递的,您可以根据参数是否作为 const 传递来修改或不修改它。但是,无论是否为 const,参数都维护对对象的引用,并且不能将引用分配为指向被调用函数内的不同对象。
Python:Python 是“pass-by-object-reference”,也就是常说的:“对象引用是按值传递的。”[阅读这里]1.调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只是在调用者中保存对象的副本。与 C++ 一样,参数可以在函数中修改或不修改 - 这取决于传递的对象类型。例如;不可变对象类型不能在被调用函数中修改,而可变对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个关键区别是更新的值会反映在被调用的函数中,而重新初始化的值不会。将新对象分配给可变变量的任何范围对于 python 中的函数都是本地的。 @blair-conrad 提供的示例非常有助于理解这一点。
我是 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
tuple
,它是表达式 a, b, c
创建的。然后,您使用 iterable unpacking 将该元组解包为单独的变量。当然,实际上,您可以将其视为“返回多个值”,但您实际上并没有这样做,您正在返回一个容器。
或者你可以使用 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 是 ac 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)
dataclasses 呢?此外,它还允许您应用类型限制(又名“类型提示”)。
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()
我同意人们在大多数情况下最好考虑不使用它的观点。
然而,当我们谈论 contexts 时,了解这种方式是值得的。
您可以设计显式上下文类。在进行原型设计时,我更喜欢数据类,只是因为很容易来回序列化它们。
干杯!
不定期副业成功案例分享
def Foo(alist): alist = [1,2,3]
将不会从调用者的角度修改列表的内容。