【转】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函数默认参数陷阱的更多相关文章

  1. Python面试题目之Python函数默认参数陷阱

    请看如下一段程序: def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_l ...

  2. python函数默认参数陷阱

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

  3. Python函数默认参数的陷阱

    默认参数实际上只有一个值 代码1 def func(l = 1): l += 1 print(l) func() func() func() 代码2 lst = [] def func(a,l = l ...

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

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

  5. python函数默认参数为可变对象的理解

    1.代码在执行的过程中,遇到函数定义,初始化函数生成存储函数名,默认参数初识值,函数地址的函数对象. 2.代码执行不在初始化函数,而是直接执行函数体. 代码实例 这要从函数的特性说起,在 Python ...

  6. Python 函数(默认参数)

    默认参数 设置默认参数时,有两点需要注意:一是必选参数在前,默认参数在后,否则python的解释器会报错二是当函数有多个参数时,把变化大的参数放前面,变化小的放后面,变化小的参数就可以作为默认参数 d ...

  7. [python]函数默认参数顺序问题

    python 函数参数定义有四类: 1.必选参数:调用函数时候必须赋值的参数. a,须以正确的顺序传入函数b,调用时的数量必须和声明时的一样 def exa(x): return x #b作为参数进入 ...

  8. python函数默认参数作用域

    当def函数参数默认值为对象时,例如列表[],字典{} 示例1:猜测一下,会输出什么??? def ddd(a,b=[]): b.append(a) return b print(ddd(1)) pr ...

  9. python函数默认参数坑

    def add(a=3,b): print a,b add(4) 这样写的话,运行的话就会报错:SyntaxError: non-default argument follows default ar ...

随机推荐

  1. windows系统中给qt工程添加第三方库

    · TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt SOURCES += main.cpp LIBS ...

  2. java基础-jdk工具包

    1. 标准工具 这些工具都是JDK提供的,通常都是长期支持的工具,JDK承诺这些工具比较好用.不同系统.不同版本之间可能会有差异,但是不会突然就有一个工具消失. 1.1 基础包 (extcheck, ...

  3. mybatis中常见的问题总结

    如下所有举例基于springboot+mybatis项目中,SSH使用mybatis的写法也一样,只是形式不同而已 问题1.org.apache.ibatis.binding.BindingExcep ...

  4. android AVD 启动时报错

    AVD启动报错 1.提示:ANDROID_SDK_ROOT is undefined / ERROR: This AVD’s configuration is missing a kernel fil ...

  5. java工程师的成长历程

    2019年2月 这是我站在刚入行的角度,对自己未来的发展做出的初步的规划,以后慢慢调整 一. 初级java工程师 1.熟悉java的基本语法,拥有面向对象的思维,熟悉javaSE的核心类库 2.学习j ...

  6. Sass和less的区别是什么?用哪个好

    什么是Sass和Less?       Sass和Less都属于CSS预处理器,那什么是 CSS 预处理器呢?        CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 ...

  7. 学习STM32F769DK-OTA例程之百度云平台建立MQTT服务器

    @2019-04-17 [小记] 百度云平台建立MQTT服务器时需要设置权限组,否则连接失败

  8. git在windows及linux环境下安装及常用命令

    git在windows下安装 下载地址:https://git-scm.com/ 默认安装即可 验证 git --version git在linux下安装 下载地址:https://mirrors.e ...

  9. HDU - 3247 Resource Archiver (AC自动机,状压dp)

    \(\quad\)Great! Your new software is almost finished! The only thing left to do is archiving all you ...

  10. MySQL表与表之间的关系

    表与表之间的关系 表1 foreign key 表2 则表1的多条记录对应表2的一条记录,即多对一 利用foreign key的原理我们可以制作两张表的多对多,一对一关系 多对多: 表1的多条记录可以 ...