【转】Python函数默认参数陷阱
请看如下一段程序:
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)
请先猜想打印的结果:
是不是这样:
[10]
[123]
[a]
False
但是,实际的打印效果

请看如下解释:
<!-- 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)两种类型。
元组(tuple)、数值型(number)、字符串(string)均为不可变对象,而字典型(dictionary)和列表型(list)的对象是可变对象。
对于可变对象来说,传址是可以改变原对象的值的,对于不可变对象来说,传址相当于多了一个指向该值(不可变)的指针
不可变对象

可变对象

函数默认参数陷阱
下面这一段程序
#! /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可以通过函数的默认值来实现静态变量的功能。
【转】Python函数默认参数陷阱的更多相关文章
- Python面试题目之Python函数默认参数陷阱
请看如下一段程序: def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_l ...
- python函数默认参数陷阱
对于学习python的人都有这样的困惑 def foo(a=[]): a.append(5) return a Python新手希望这个函数总是返回一个只包含一个元素的列表:[5].结果却非常不同,而 ...
- Python函数默认参数的陷阱
默认参数实际上只有一个值 代码1 def func(l = 1): l += 1 print(l) func() func() func() 代码2 lst = [] def func(a,l = l ...
- 使用可变对象作为python函数默认参数引发的问题
写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...
- python函数默认参数为可变对象的理解
1.代码在执行的过程中,遇到函数定义,初始化函数生成存储函数名,默认参数初识值,函数地址的函数对象. 2.代码执行不在初始化函数,而是直接执行函数体. 代码实例 这要从函数的特性说起,在 Python ...
- Python 函数(默认参数)
默认参数 设置默认参数时,有两点需要注意:一是必选参数在前,默认参数在后,否则python的解释器会报错二是当函数有多个参数时,把变化大的参数放前面,变化小的放后面,变化小的参数就可以作为默认参数 d ...
- [python]函数默认参数顺序问题
python 函数参数定义有四类: 1.必选参数:调用函数时候必须赋值的参数. a,须以正确的顺序传入函数b,调用时的数量必须和声明时的一样 def exa(x): return x #b作为参数进入 ...
- python函数默认参数作用域
当def函数参数默认值为对象时,例如列表[],字典{} 示例1:猜测一下,会输出什么??? def ddd(a,b=[]): b.append(a) return b print(ddd(1)) pr ...
- python函数默认参数坑
def add(a=3,b): print a,b add(4) 这样写的话,运行的话就会报错:SyntaxError: non-default argument follows default ar ...
随机推荐
- 分布式存储ceph——(4)ceph 添加/删除osd
一.添加osd: 当前ceph集群中有如下osd,现在准备新添加osd:
- Linux安装Sqlite
下载SQLite源代码sqlite-3.6.23.1.tar.gz 复制sqlite-3.6.23.1.tar.gz到linux上的/usr/src目录 解压源代码 tar -xvzf sqlite- ...
- Linux(Ubuntu)使用日记------trash-cli防止误删文件
1.安装过程 cd /tmp git clone https://github.com/andreafrancia/trash-cli cd trash-cli sudo python setup.p ...
- 【MySQL 读书笔记】当我们在执行该查询语句的时候我们在干什么
看了非常多 MySQL 相关的书籍和文章,没有看到过如此优秀的专栏.所以未来一段时间我会梳理读完该专栏的所学所得. 当我们在执行该查询语句的时候我们在干什么 mysql> select * fr ...
- Capability配置简介
什么是Capability desired capability的功能是配置Appium会话.他们告诉Appium服务器您想要自动化的平台和应用程序. Desired Capabilities是一组设 ...
- java &与&& |与||的区别
一.与操作和或操作的区别 (1)在Java程序中,使用与操作,要求所有表达式的判断结果都是TRUE,才为真,若有一个为FALSE,那么最终判断结果则为FALSE (2)使用或操作,只要其中有一个表达式 ...
- springdata 查询思路:基本的单表查询方法(id,sort) ---->较复杂的单表查询(注解方式,原生sql)--->实现继承类---->复杂的多表联合查询 onetomany
springdata 查询思路:基本的单表查询方法(id,sort) ---->较复杂的单表查询(注解方式,原生sql)--->实现继承类---->复杂的多表联合查询 onetoma ...
- java篇 之 操作符
操作符:1.赋值操作符 用(+= ,^=...不会改变类型,如果用 = 会进行隐式转换类型) short x = 0; int i = 123456; x += i;//编译通过 x= x + i;/ ...
- Ubuntu16设置Redis开机自启动
Ubuntu16设置Redis开机自启动 Ubuntu16设置Redis开机自启动 设置条件: -Ubuntu16.04 -Redis-4.0.11 在redis目录下找到 utils/redi ...
- Monte Carlo Method(蒙特·卡罗方法)
0-故事: 蒙特卡罗方法是计算模拟的基础,其名字来源于世界著名的赌城——摩纳哥的蒙特卡罗. 蒙特卡罗一词来源于意大利语,是为了纪念王子摩纳哥查理三世.蒙特卡罗(MonteCarlo)虽然是个赌城,但很 ...