python: 可变参数

结论:定义Python函数的默认参数时,默认参数必须指向不变对象!

事实上,编写程序时如 果可以设计成不变对象,就应该尽量设计成不变对象。

以下例子来自 The Hitchhiker’s Guide to Python .

假如定义函数:

def append_to(element, to=[]):

    to.append(element)

    return to

调用函数如下:

my_list = append_to(12)

print(my_list)

my_other_list = append_to(42)

print(my_other_list)

我们可能期待这样的结果:

[12]

[42]

实际上得到的是:

[12]

[12, 42]

究其原因,是因为Python默认参数的值在函数定义时即已经算好了,而不会在再次调用时再重新计算。在上述例子中,默认参数to也是一个变量,它指向了对象[],每次函数调用时,如果对象的内容已经改变,那么默认参数to指向内容自然也变了。

对List而言,List在定义时即被创立,只要名字不变,相同的List会一直被后续的调用使用,所以第二次调用时,得到的是已经被改变的List。

 

想避免上述问题,不使用可变参数即可。

假如真的需要传递一个List,可以使用如下方法:

def append_to(element,to=None):

    if to is None:

        to=[]

    return to.append(element)

×上面表述多少有点易混淆,参数里的to和定义成List的to是否为不同的东西?它们是否指向了不同的地方?如果是,那么不应该使用同样的名字,因为那样容易使人以为两者是相同的。

 

一些额外的扩展。

这里设计到一个问题,即Python函数的参数是如何传递的?是call-by-name?还是call-by-reference?正确的说两者都不是,Python是call-by-object。

Python有一句话很重要:Everything is an object.

Python中“给一个变量赋值”,这里“变量”更准确的术语是names,“赋值”更准确的表述是“把name与一个object绑定起来”。

请看以下例子体会Python与其他语言的区别:

比如在C++中,

string str=”ABC”;

//…

str=”DEF”;

str指向内存中某一地址,该地址里面存的值是”ABC”;到后面,该内存里的内容被改写,变成”DEF”。

下面看在Python中的情况,

str=’ABC’

#…

str=’DEF’

一开始把name str与一个字符串对象’ABC’绑定;到后面name str与另一个字符串对象’DEF’绑定,但是原先的’ABC’对象并不受影响。

不难理解,一个object可以同时对应多个name。

因此,在Python中无论赋值也好函数调用也好,所做的只是把”作用域”与”对象”绑定起来,如果该对象是可变的,那么它的变化会超出”作用域“之外。

请看下面例子:

def (ele):

    ele.append(1)

    ele=[2]

m=[]

f(m)

print(m)

结果会是[1]而不是[2],因为List是可变的,append方法改变了m,但是赋值局部变量ele的行为对外面作用域是没有影响的,Python中赋值是给name绑定一个新对象,而不是改变对象。

posted on 2018-11-23 11:33  freshair_cn  阅读(582)  评论(0编辑  收藏  举报

导航