26. 如何按引用传值

题目

关于参数是按值传递还是按引用传递,Python文档写得不够清晰,下面的代码中值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,这句话有2层含义:

  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_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_stringout_string引用的拷贝,the_string指向了一个新字符串,但是不能改变out_string的指向。

如何解决这个问题?

可以返回新的值,这并不会改变参数传递的方式,但是能够得到希望返回的信息。

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

虽然这看起来不够简洁。

geekcircle            updated 2018-09-18 23:47:07

results matching ""

    No results matching ""