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

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

    def change(self, var):
        var = '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

其他回答

在这种情况下,方法Change中名为var的变量被分配了对self.variable的引用,并且您立即将字符串分配给var。它不再指向self.variable.下面的代码片段显示了如果您修改了var和self.variaable指向的数据结构(在本例中是一个列表)会发生什么:

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

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

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 . . . ")

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

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

So:

如果您将一个可变对象传递给一个方法,该方法将获得对同一对象的引用,您可以根据自己的喜好对其进行变异,但如果您在方法中重新绑定引用,外部作用域将对此一无所知,完成后,外部引用仍将指向原始对象。如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至无法更改对象。

为了更加清楚,让我们举几个例子。

列表-可变类型

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

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引用的副本,我们让_list指向一个新列表,但无法更改outer_list指向的位置。

字符串-不可变类型

它是不可变的,因此我们无法更改字符串的内容

现在,让我们尝试更改引用

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

同样,由于该_string参数是按值传递的,因此为其分配一个新字符串不会对方法外部的代码产生任何影响。The_string是outer_string引用的副本,我们让_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])

虽然这看起来有点麻烦。

(编辑-布莱尔更新了他广受欢迎的答案,使其准确无误)

我认为重要的是要注意到,目前获得最多选票的帖子(布莱尔•康拉德),虽然其结果正确,但却具有误导性,并且根据其定义,几乎不正确。虽然有许多语言(如C)允许用户通过引用传递或通过值传递,但Python不是其中之一。

大卫·库尔纳波的回答指向了真正的答案,并解释了为什么布莱尔·康拉德帖子中的行为似乎是正确的,而定义却不正确。

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

如果你想要这种行为,Blair Conrad的回答很好。但如果你想知道Python既不是通过值传递,也不是通过引用传递的根本原因,请阅读大卫·库尔纳波的答案。

想想通过赋值而不是通过引用/值传递的东西。这样,只要你明白在正常任务中发生了什么,就会很清楚发生了什么。

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

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

由于不可变类型不能被修改,它们看起来像是通过值传递的——将int传递给函数意味着将int分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。