【转】Python函数默认参数陷阱

【转】Python函数默认参数陷阱html

请看以下一段程序:express

def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_list(123, []) list3 = extend_list('a') print(list1) print(list2) print(list3) print(list1 is list3)

请先猜测打印的结果:app

是否是这样:函数

[10] [123] [a] False

 

可是,实际的打印效果lua

 

 请看以下解释:spa

<!-- lang: python --> # 函数的定义至关于一次类型构造,默认值只在此时解析一次。 # 而函数调用时不会从新执行默认参数的构造。因此,若是使用了字典,列表这样的可变类型。 # 而又要在函数体内修改它,可能会出现意想不到的效果. def a(b=[]): b.append('hi') print b In [11]: a() ['hi'] In [12]: a() ['hi', 'hi'] In [13]: a(['2']) ['2', 'hi'] In [14]: a() ['hi', 'hi', 'hi'] In [15]: a.func_defaults Out[15]: (['hi', 'hi', 'hi'],) # 解决方法:参数默认值使用None赋值 def(b = None): b = b or [] pass # 类属性也有相似问题 class A(object): x = [] def __init__(self, c): self.x.append(c) # 这里的x搜索到类级别的x了而非实例的, # 因实例级别的x未事先定义 In [36]: a1, a2 = A(1), A(2) In [37]: a1.x, a2.x Out[37]: ([1, 2], [1, 2]) # 解决方法, 实例级别的属性事先定义 class B(object): x = [] def __init__(self, c): self.x = [] # 此处实例属性有x,因此先搜索到此 self.x.append(c) In [38]: b1, b2 = B(1), B(2) In [39]: b1.x, b2.x Out[39]: ([1], [2])

 

 

python可变对象作默认参数陷阱

可变对象与不可变对象

python中,万物皆对象。python中不存在所谓的传值调用,一切传递的都是对象的引用,也能够认为是传址。指针

python中,对象分为可变(mutable)和不可变(immutable)两种类型。code

元组(tuple)、数值型(number)、字符串(string)均为不可变对象,而字典型(dictionary)和列表型(list)的对象是可变对象。htm

对于可变对象来讲,传址是能够改变原对象的值的,对于不可变对象来讲,传址至关于多了一个指向该值(不可变)的指针

不可变对象

 

可变对象

 

 

函数默认参数陷阱

下面这一段程序

#! /usr/bin/env python # -*- coding: utf-8 -*- class demo_list: def __init__(self, l=[]): self.l = l def add(self, ele): self.l.append(ele) def appender(ele): obj = demo_list() obj.add(ele) print obj.l if __name__ == "__main__": for i in range(5): appender(i)

 

输出结果是多少?

[0] [0, 1] [0, 1, 2] [0, 1, 2, 3] [0, 1, 2, 3, 4]

而不是想象的

[0] [1] [2] [3] [4]

而若是想达到第二种效果,只需将obj = demo_list() 改成obj = demo_list(l=[]) 便可

默认参数原理

官方文档中的一句话:

Default values are computed once, then re-used.

默认值是被重复使用的

Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.

因此当默认参数值是可变对象的时候,那么每次使用该默认参数的时候,其实更改的是同一个变量

当python执行def语句时,它会根据编译好的函数体字节码和命名空间等信息新建一个函数对象,而且会计算默认参数的值。函数的全部构成要素都可经过它的属性来访问,好比能够用funcname属性来查看函数的名称。全部默认参数值则存储在函数对象的defaults_属性中,它的值为一个列表,列表中每个元素均为一个默认参数的值

其中默认参数至关于函数的一个属性

Functions in Python are first-class objects, and not only a piece of code.

咱们能够这样解读:函数也是对象,所以定义的时候就被执行,默认参数是函数的属性,它的值可能会随着函数被调用而改变。其余对象不都是如此吗?

避免

使用可变参数做为默认值可能致使意料以外的行为。为了防止出现这种状况,最好使用None值,而且在后面加上检查代码

 
def __init__(self, l=None): if not l: self.l = [] else: self.l = l

 

在这里将None用做占位符来控制参数l的默认值。不过,有时候参数值多是任意对象(包括None),这时候就不能将None做为占位符。你能够定义一个object对象做为占位符,以下面例子:

 
sentinel = object() def func(var=sentinel): if var is sentinel: pass else: print var

 

修饰器方法

Python cookbook中也提到了这个方法,为了不对每个函数中每个可能为None的对象进行一个if not l的判断,使用可更优雅的修饰器方法

import copy def freshdefault(f): fdefaults = f.func_defaults def refresher(*args,**kwds): f.func_defaults = deepcopy(fdefaults) return f(*args,**kwds) return refresh

 

这段代码也再次认证了默认参数是函数的一个属性这一事实

扩展

python中函数的默认值只会被执行一次,(和静态变量同样,静态变量初始化也是被执行一次)。Python能够经过函数的默认值来实现静态变量的功能。

相关文章
相关标签/搜索