提问人:David Sykes 提问时间:6/12/2009 最后编辑:Karl KnechtelDavid Sykes 更新时间:7/13/2023 访问量:2024375
如何通过引用传递变量?
How do I pass a variable by reference?
问:
我写了这个类来测试:
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.change(self.variable)
print(self.variable)
def change(self, var):
var = 'Changed'
当我尝试创建实例时,输出是 .因此,Python 中的参数似乎是按值传递的。这是对的吗?如何修改代码以获得按引用传递的效果,以便输出是?Original
Changed
有时人们会惊讶地发现,像 x = 1 这样的代码(其中
x 是参数名称)不会影响调用者的参数,但像 x[0] = 1
这样的代码却会影响调用方的参数。发生这种情况的原因是,尽管有
=
语法,但项赋值和切片赋值是改变现有对象的方法,而不是重新赋值变量。有关详细信息,请参阅为什么函数可以修改调用方感知到的某些参数,而不能修改其他参数?
另请参阅按引用传递与按值传递有什么区别?,了解重要的、与语言无关的术语讨论。
答:
在本例中,为方法中标题的变量分配了对 的引用,并立即将字符串分配给 。它不再指向 .以下代码片段显示了如果修改 和 指向的数据结构,在本例中为列表,会发生什么情况:var
Change
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']
>>>
我相信其他人可以进一步澄清这一点。
参数通过赋值传递。这背后的基本原理有两个:
- 传入的参数实际上是对对象的引用(但引用是按值传递的)
- 某些数据类型是可变的,但其他数据类型则不是
所以:
如果将可变对象传递到方法中,则该方法将获得对同一对象的引用,您可以随心所欲地对其进行更改,但是如果您在方法中重新绑定引用,则外部作用域将对此一无所知,并且在完成之后,外部引用仍将指向原始对象。
如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至无法更改对象。
为了更清楚,让我们举一些例子。
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_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])
虽然这似乎有点麻烦。
评论
def Foo(alist): alist = [1,2,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' )
评论
想想东西是通过赋值传递的,而不是通过引用/值传递的。这样,只要您了解正常任务期间发生的情况,就可以始终清楚发生了什么。
因此,当将列表传递给函数/方法时,该列表将分配给参数名称。追加到列表将导致列表被修改。在函数中重新分配列表不会更改原始列表,因为:
a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b # prints [1, 2, 3, 4] ['a', 'b']
由于不可变类型无法修改,因此它们似乎是按值传递的 - 将 int 传递到函数中意味着将 int 分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。
评论
它既不是按值传递,也不是按引用传递,而是按对象调用。请看弗雷德里克·伦德(Fredrik Lundh)的这篇文章:
这是一个重要的引述:
"...变量 [names] 不是对象;它们不能用其他变量来表示,也不能用对象来指代。
在您的示例中,当调用该方法时,将为其创建一个命名空间;并成为该命名空间中 String 对象的名称。然后,该对象在两个命名空间中具有名称。接下来,绑定到一个新的字符串对象,因此该方法的命名空间会忘记 .最后,该命名空间被遗忘,字符串也随之被遗忘。Change
var
'Original'
var = 'Changed'
var
'Original'
'Changed'
评论
swap
a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
我通常使用的一个简单的技巧是将其包装在一个列表中:
def Change(self, var):
var[0] = 'Changed'
variable = ['Original']
self.Change(variable)
print variable[0]
(是的,我知道这可能会很不方便,但有时这样做很简单。
评论
问题来自对 Python 中变量的误解。如果你习惯了大多数传统语言,你就会对以下顺序发生的事情有一个心智模型:
a = 1
a = 2
您认为这是存储值的内存位置,然后更新以存储该值。这不是 Python 中的工作方式。相反,它以对具有 值的对象的引用开始,然后被重新赋值为对具有 的对象的引用。这两个对象可以继续共存,即使不再指第一个对象;事实上,它们可以由程序中任意数量的其他引用共享。a
1
2
a
1
2
a
使用参数调用函数时,将创建一个引用传入的对象的新引用。这与函数调用中使用的引用是分开的,因此无法更新该引用并使其引用新对象。在您的示例中:
def __init__(self):
self.variable = 'Original'
self.Change(self.variable)
def Change(self, var):
var = 'Changed'
self.variable
是对 String 对象的引用。调用时,将创建对对象的第二个引用。在函数中,您将引用重新分配给不同的字符串对象,但引用是独立的,不会更改。'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
从技术上讲,Python 始终使用按引用传递值。我将重复我的另一个答案来支持我的发言。
Python 始终使用按引用传递值。没有任何例外。任何变量赋值都意味着复制参考值。也不例外。任何变量都是绑定到引用值的名称。总是。
您可以将引用值视为目标对象的地址。使用时,该地址会自动取消引用。这样,在使用引用值时,您似乎直接使用目标对象。但中间总有一个参考,再跳到目标一步。
下面是证明 Python 使用引用传递的示例:
如果参数是按值传递的,则无法修改外部参数。绿色是目标对象(黑色是存储在里面的值,红色是对象类型),黄色是内存,里面有参考值,画成箭头。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部词典。(它实际上也可以画成一个绿色的椭圆。颜色和形状只说它是内部的。lst
您可以使用 id()
内置函数来了解引用值是什么(即目标对象的地址)。
在编译语言中,变量是能够捕获类型值的内存空间。在 Python 中,变量是绑定到引用变量的名称(在内部捕获为字符串),该引用变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分存储目标的引用值。
引用值在 Python 中是隐藏的。没有任何用于存储引用值的显式用户类型。但是,您可以使用列表元素(或任何其他合适的容器类型的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上并不包含在容器中——只有对元素的引用才是。
评论
这是对 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)
这显然会改变对象。这个答案很好地解释了这一点。
评论
list = [1, 2, 3]
list
list[:] = [1, 2, 3]
list
[0, 1] = [1, 2, 3]
[0, 1] = [1, 2, 3]
alist[2]
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]
希望这能为您澄清问题。
评论
id()
locals()
dict
正如你所说,你需要有一个可变的对象,但我建议你检查一下全局变量,因为它们可以帮助你,甚至解决这种问题!
例:
>>> 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 中没有变量
理解参数传递的关键是停止思考“变量”。Python 中有名称和对象,它们一起 看起来像变量,但始终区分这三个变量很有用。
- Python 有名称和对象。
- 赋值将名称绑定到对象。
- 将参数传递到函数中也会将名称(函数的参数名称)绑定到对象。
这就是它的全部内容。可变性与这个问题无关。
例:
a = 1
这会将名称绑定到保存值 1 的整数类型的对象。a
b = x
这会将名称绑定到该名称当前绑定到的同一对象。
之后,这个名字就和这个名字没有关系了。b
x
b
x
请参阅 Python 3 语言参考中的第 3.1 和 4.2 节。
如何阅读问题中的示例
在问题中所示的代码中,该语句将名称(在函数范围内)绑定到保存值的对象,赋值(在函数主体中)再次将相同的名称分配给其他对象(恰好也包含字符串,但可能完全是其他对象)。self.Change(self.variable)
var
Change
'Original'
var = 'Changed'
Change
如何通过引用传递
因此,如果您要更改的东西是可变对象,则没有问题,因为所有内容都是通过引用有效地传递的。
如果它是一个不可变的对象(例如布尔、数字、字符串),那么要走的方法是将其包装在一个可变的对象中。
快速而肮脏的解决方案是一个单元素列表(而不是 ,pass 和 in the function modify )。
更 python 化的方法是引入一个普通的单属性类。该函数接收类的实例并操作该属性。self.variable
[self.variable]
var[0]
评论
int
Integer
Integer
int
a = 1; b = a; a++ # doesn't modify b
+= 1
++
我发现其他答案相当长且复杂,因此我创建了这个简单的图表来解释 Python 处理变量和参数的方式。
评论
B[0] = 2
B = 2
A=B
B=A
mutable
immutable
append
append
这里的答案有很多见解,但我认为这里没有明确提到另一点。引用 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
因此,对未声明为全局变量的赋值将创建一个新的局部对象,并断开与原始对象的链接。
Python 的逐个传递方案与 C++ 的引用参数选项并不完全相同,但事实证明,它在实践中与 C 语言(和其他语言)的参数传递模型非常相似:
- 不可变参数实际上是“按值”传递的。整数和字符串等对象是通过对象引用而不是通过复制传递的,但由于您无论如何都无法更改不可变对象,因此效果很像制作副本。
- 可变参数实际上是“通过指针”传递的。列表等对象 而字典也是通过对象引用传递的,这与 C 的方式类似 将数组作为指针传递 - 可以在函数中就地更改可变对象, 很像 C 数组。
除了所有关于这些东西在 Python 中如何工作的精彩解释之外,我没有看到针对该问题的简单建议。正如您似乎在创建对象和实例一样,处理实例变量和更改它们的 Python 方式如下:
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
若要模拟按引用传递对象,请将其包装在一个单项列表中:
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)
分配给列表的元素会改变列表,而不是重新分配名称。由于列表本身具有引用语义,因此更改将反映在调用方中。
评论
p
是对可变列表对象的引用,该对象又存储该对象。引用 'p' 被传递到 .在内部 ,将创建一个新引用(称为新引用),该引用指向指向的同一列表对象。但是,由于列表是可变的,因此对列表的更改在两个引用中都可见。在本例中,您使用引用更改索引 0 处的对象,以便它随后存储该对象。对列表对象的更改是使用 完成的,但此更改对 可见。obj
changeRef
changeRef
ref
p
ref
PassByReference('Michael')
ref
p
p
ref
PassByReference('Michael')
p[0].name
Michael
ref
name
PassByReference
obj
obj.name
Peter
Mark Ransom
PassByReference
PassByReference
我使用以下方法快速将一些 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
虽然通过引用传递并不适合 Python,并且应该很少使用,但有一些解决方法实际上可以将当前分配给局部变量的对象,甚至可以从被调用函数内部重新分配局部变量。
基本思想是有一个函数可以进行这种访问,并且可以作为对象传递给其他函数或存储在类中。
一种方法是在包装函数中使用(用于全局变量)或(用于函数中的局部变量)。global
nonlocal
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 文档建议更改字典可能不起作用,但它似乎对我有用。ByRef
wrapped
locals
评论
鉴于 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 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#:基元类型(包括字符串)按值传递 (copy)。引用类型是通过引用(地址复制)传递的,因此调用方可以看到在被调用函数中的参数中所做的所有更改。
C++:允许按引用传递或按值传递。如果参数是通过引用传递的,则可以修改它,也可以不修改它,具体取决于该参数是否作为 const 传递。但是,无论是否常量,该参数都保持对对象的引用,并且不能将引用分配给指向被调用函数中的其他对象。
蟒:Python 是“按对象引用传递”,其中经常有人说:“对象引用是按值传递的。(在这里阅读)。调用方和函数都引用同一个对象,但函数中的参数是一个新变量,它只是在调用方中保存对象的副本。与 C++ 一样,参数可以在函数中修改或不修改。这取决于传递的对象类型。例如,不可变对象类型不能在被调用的函数中修改,而可变对象可以更新或重新初始化。
更新或重新分配/重新初始化可变变量之间的一个关键区别是,更新的值会反映回被调用的函数中,而重新初始化的值不会。将新对象赋值给可变变量的任何范围都是 python 中函数的本地范围。@blair-conrad 提供的例子非常适合理解这一点。
评论
由于字典是通过引用传递的,因此可以使用 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
由于似乎没有提到,因此模拟引用的方法,例如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
这对于“仅输出引用”或具有多个线程/进程的情况(通过使更新函数线程/多处理安全)最有用。
显然,上面不允许读取值,只能更新它。
评论
我是 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
或者,您可以使用如下所示的 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 整数,显然是通过引用传递的。但是,您必须小心,因为可能会发生奇怪的事情,因此不建议这样做。
这很可能不是最可靠的方法,但这是有效的,请记住,您正在重载内置的 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)
使用数据类。此外,它还允许您应用类型限制(又称“类型提示”)。
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()
我同意人们的观点,在大多数情况下,您最好考虑不要使用它。
然而,当我们谈论上下文时,以这种方式了解是值得的。
不过,您可以设计一个显式的上下文类。在制作原型时,我更喜欢数据类,因为它很容易来回序列化它们。
评论
关于这个问题已经有很多很好的答案(或者让我们说意见),我已经读过了,但我想提一个缺失的答案。来自 Python 文档的 FAQ 部分。我不知道发布此页面的日期,但这应该是我们真正的参考:
请记住,参数是在 Python 中通过赋值传递的。因为 Assignment 只是创建对对象的引用,没有别名 在调用方和被调用方的参数名称之间,等等 按引用调用本身。
如果您有:
a = SOMETHING
def fn(arg):
pass
你称它为,你正在做你在任务中所做的。所以这会发生:fn(a)
arg = a
将创建对 的附加引用。变量只是符号/名称/引用。他们不“持有”任何东西。SOMETHING
评论