繁体   English   中英

如何通过引用传递变量?

[英]How do I pass a variable by reference?

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

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

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

参数通过 assignment 传递 这背后的理由是双重的:

  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的引用,而不是它的副本,我们可以使用 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_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 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 的回答所示,您可以返回新值。 这不会改变传入的方式,但可以让你得到你想要的信息:

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'

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

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

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

这是一个重要的报价:

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

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

想想通过赋值而不是通过引用/按值传递的东西。 这样一来,只要您了解正常分配期间发生的事情,就会很清楚发生了什么。

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

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 中有名称和对象,它们一起看起来像变量,但始终区分这三者是有用的。

  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的方法是引入一个简单的、单一属性的类。 该函数接收该类的一个实例并操作该属性。

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 x = []; y = x; x.append(10); print y x = []; y = x; x.append(10); print y将打印[10] 空列表已创建。 “x”和“y”都指向同一个列表。 append方法改变(更新)列表对象(如向数据库中添加记录),结果对“x”和“y”都可见(就像数据库更新对该数据库的每个连接都是可见的)。

希望能为您澄清问题。

从技术上讲, Python 总是使用通过引用值传递 我将重复我的其他答案以支持我的陈述。

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

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

这是证明 Python 使用按引用传递的示例:

传递参数的图解示例

如果参数是按值传递的,则无法修改外部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 的回答。

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

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 以下代码片段显示了如果修改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']
>>> 

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

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

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

这里有很多关于答案的见解,但我认为这里没有明确提到另外一点。 引用 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 的全局变量的赋值会创建一个新的局部对象并断开与原始对象的链接。

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

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

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

通过引用传递对象有一个小技巧,即使语言无法实现。 它也适用于 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

鉴于 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'

由于字典是通过引用传递的,因此您可以使用 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 并且应该很少使用,但有一些变通方法实际上可以将当前分配给局部变量的对象或什至从被调用函数内部重新分配局部变量。

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

一种方法是在包装函数中使用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 是“传递对象引用”,也就是常说的:“对象引用是按值传递的。”[阅读这里] 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

或者你可以使用 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)

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

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

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

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

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

干杯!

  1. Python 为每个对象分配一个唯一标识符,并且可以使用 Python 的内置id()函数找到该标识符。 可以验证函数调用中的实际参数和形式参数是否具有相同的 id 值,这表明虚拟参数和实际参数引用的是同一个对象。

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

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

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 中的任何变量都存储一个值

将一个变量分配给另一个变量,包括一个函数参数,会创建该值的副本

对于标量变量,该值只是要检索的值(数字、字符串等)。

对于list ,该 value 是引用另一个 value/s位置的地址

还有global 关键字,它将变量的范围扩展到应用的函数。

标量示例

标量示例

列表示例

列表示例

全局示例

在此处输入图像描述

参考

有一个 Python 课程解释了这些概念,还有一个沙箱来测试代码,以下是相关部分:

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

class PassByReference:
    def __init__(self, variable, pass_by_reference=True):
        self._variable_original = 'Original'
        self._variable = variable
        self._pass_by_reference = pass_by_reference # False => pass_by_value
        self.change(self.variable)
        print(self)

    def __str__(self):
        print(self.get_variable())

    def get_variable(self):
        if pass_by_reference == True:
            return self._variable
        else:
            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'
    

我解决了一个类似的要求,如下所示:

要实现更改变量的成员函数,请不要传递变量本身,而是传递包含引用变量的setattrfunctools.partial 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")

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

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

@refproperty('myvar')
class T():
    pass

def f(x):
   x.value=6

y=T()
y.myvar=3
f(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__=newinit
        return klass
    return decorator

class RefVar(object):
    def __init__(self, value=None):
        self.value = value
    def get(self,*args):
        return self.value
    def 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=4
b=3
c=8

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

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

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

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

随便玩玩,然后你就知道了。

  1. 传递一个字符串

在此处输入图像描述

  1. 传递列表

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM