请看如下一段程序:

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([''])
['', '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可以通过函数的默认值来实现静态变量的功能。

参考

[1]陷阱!python参数默认值
[2]python tips - 注意函数参数的默认值
[3]Default Parameter Values in Python

Python面试题目之Python函数默认参数陷阱的更多相关文章

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

    [转]Python函数默认参数陷阱 阅读目录 可变对象与不可变对象 函数默认参数陷阱 默认参数原理 避免 修饰器方法 扩展 参考 请看如下一段程序: def extend_list(v, li=[]) ...

  2. python函数默认参数陷阱

    对于学习python的人都有这样的困惑 def foo(a=[]): a.append(5) return a Python新手希望这个函数总是返回一个只包含一个元素的列表:[5].结果却非常不同,而 ...

  3. Python面试题目之Python的复制和赋值浅析

    python采用的是引用变量的结构,也就说如果你对一个变量赋值,并不是给这个变量开辟了一块内存空间而是将一个对象的内存空间地址告诉了这个变量,这样做的好处是便于管理,节省内存空间,便于内存释放等等.但 ...

  4. Python进阶-函数默认参数

    Python进阶-函数默认参数 写在前面 如非特别说明,下文均基于Python3 一.默认参数 python为了简化函数的调用,提供了默认参数机制: def pow(x, n = 2): r = 1 ...

  5. Python学习笔记五,函数及其参数

    在Python中如何自定义函数:其格式为 def 函数名(函数参数): 内容

  6. C++函数重载遇到了函数默认参数情况

    一.C++中的函数重载 什么是函数重载? 我的理解是: (1)用一个函数名定义不同的函数: (2)函数名和不同参数搭配时函数会有不同的含义: 举例说明: #include <stdio.h> ...

  7. 如何在ES5与ES6环境下处理函数默认参数

    函数默认值是一个很提高鲁棒性的东西(就是让程序更健壮)MDN关于函数默认参数的描述:函数默认参数允许在没有值或undefined被传入时使用默认形参. ES5 使用逻辑或||来实现 众所周知,在ES5 ...

  8. python中函数的默认参数陷阱问题

    其实也不能说是陷阱,只是一个不容易注意到的地方,尤其是有其他java/c++类编程语言经验的人员,这里涉及到python的一个特点,所以笔者说是陷阱只是一个噱头而已. def test(item, b ...

  9. 使用可变对象作为python函数默认参数引发的问题

    写python的都知道,python函数或者方法可以使用默认参数,比如 1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello ...

随机推荐

  1. mysql学习(2)-MySQL服务器优化

    调优思路: 1.数据库设计与规划--以后再修该很麻烦,估计数据量,使用什么存储引擎 2.数据的应用--怎样取数据,sql语句的优化 3.mysql服务优化--内存的使用,磁盘的使用 4.操作系统的优化 ...

  2. Advancing The Realtime Web With RethinkDB

    RethinkDB is an open-source distributed database built to store JSON and scale with very little effo ...

  3. C++ XML解析之TinyXML篇[转]

    最 近使用TinyXML进行C++ XML解析,感觉使用起来比较简单,很容易上手,本文给出一个使用TinyXML进行XML解析的简单例子,很多复杂的应用都可以基于本例子的方法来完 成.以后的文章里会讲 ...

  4. c++ 11 多线程教学(1)

    本篇教学代码可在GitHub获得:https://github.com/sol-prog/threads. 在之前的教学中,我展示了一些最新进的C++11语言内容: 1. 正则表达式(http://s ...

  5. MVC View基础(转)

    View主要用于呈现数据.由于Controller和相关的Service已经处理完业务逻辑并将结果打包成model实体,View只需要怎么去获得model并将其转为Html 1选择需要渲染的视图 在上 ...

  6. 利用Socket与硬件通信(智能家居)

    前几天做一个智能家居APP,硬件段使用的是ESP8266WIFI模块,其实不管是WIFI模块还是蓝牙,通信都是同样一个道理,获取IP和端口来进行通信. 我是通过XCOM v2.0 发送信息,移动端接收 ...

  7. C#中判断DataReader是否为空的代码

    下面的内容是关于C#中判断DataReader是否为空的内容. if(DataReader.HasRows){}

  8. 异常处理之IIS配置加载出错

    问题详情:  一台部署在海外服务器,在管理IIS过程中,出现问题 There was an error when trying to connect. Do you want > to rety ...

  9. 事务理解及Spring中的事务

    一.隔离级别理解 1.脏读 首先理解,一个事务对数据进行了改变,尽管该事务尚未提交,但此时其他事务中的查询语句(注意一定是处于事务中的语句,不处于事务中的语句查到的是正常的)查到的数据,是该事务修改之 ...

  10. Shell教程 之第一个shell脚本

    1.第一个shell脚本 打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行 输入一些代码 #!/bi ...