写python的都知道,python函数或者方法可以使用默认参数,比如

 def foo(arg=None):
print(arg) foo() foo("hello world")

一个很简单的函数,参数arg默认使用None,当调用foo函数时,可以传入一个参数,也可以不传入参数,运行结果如下

None
hello world

这很好理解。默认参数是python一个很好的特性。

但是如果使用可变对象作为默认参数,就会引发问题。之前写过一个脚本,bug不断,后来终于找到了原因。下面引用Fluent python中的一个例子加以说明。

废话不多说,直接上代码

 """可变对象作为默认参数传入到函数引发的问题"""

 class Bus:
def __init__(self, passengers=[]):
self.passengers = passengers def pick(self, a_passenger):
self.passengers.append(a_passenger) def drop(self, a_passenger):
self.passengers.remove(a_passenger) if __name__ == '__main__':
bus1 = Bus(['Alice', 'Jeff', 'ethan'])
print(bus1.passengers)
bus1.pick("Simon")
print(bus1.passengers) # 到此为止并没什么问题 # 接下来问题来了, 创建了两个Bus对象,并进行操作。
bus2 = Bus()
print(bus2.passengers)
bus2.pick("Alice") # "Alice"上了bus2
print(bus2.passengers) # 打印bus2的乘客列表
bus3 = Bus() # 创建另一个Bus实例,同样不传入参数(使用默认[]参数)
print(bus3.passengers) # 打印bus3的乘客列表 bus3.pick("alex")
print(bus3.passengers)
print(bus2.passengers)
print(bus3.passengers is bus2.passengers) print(bus3.passengers is bus1.passengers)

下面来看运行结果

['Alice', 'Jeff', 'ethan']
['Alice', 'Jeff', 'ethan', 'Simon']
[]
['Alice']
['Alice']
['Alice', 'alex']
['Alice', 'alex']
True
False

从结果可以看出,bus1这个对象似乎没什么问题,而bus2和bus3对象的passengers属性似乎是同一个。为什么会这样呢?

我们先来分析下Bus这个类

定义了一个Bus类,有一个__init__方法,pick方法和drop方法。__init__方法有一个默认参数,即passengers=[]。

大家应该都知道在python中列表对象([])是可变对象。

当创建bus1对象的时候,传入了实实在在的passengers参数(一个非空列表)

当创建bus2和bus3对象的时候,没有传入参数,所以会使用默认参数([])

然后我对这几个Bus实例进行操作(调用pick或drop方法,即插入或者删除passengers列表中的乘客)

从结果可以很直观的看出来,bus1.passengers 并没什么问题,一切按照我们的预期进行。但是bus2的乘客"Alice"为什么会出现在bus3上? bus3的乘客“alex”为什么又会出现在bus2上???

其实这就是用可变对象作为默认参数引发的后果。因为当使用默认参数定义了一个函数/方法之后,加载模块的时候已经初始化了这个可变对象,所以当你调用这个函数/方法的时候,如果你未传入参数(即使用了默认参数), 则对函数/方法的不同调用,其实传入的是同一个可变对象(上面例子中是一个空列表)。

下面请看

 class Bus:
def __init__(self, passengers=[]):
self.passengers = passengers def pick(self, a_passenger):
self.passengers.append(a_passenger) def drop(self, a_passenger):
self.passengers.remove(a_passenger) if __name__ == '__main__':
print(Bus.__init__.__defaults__)

运行结果

([],)
在这个例子中,并没有初始化Bus类, 而__init__方法的__defaults__属性,是一个空列表, 也就是说,在完成__init__方法定义的时候,就已经初始化了默认参数(一个空列表), 因为列表是可变对象,所以后续对Bus类的引用就会引发问题。

至此,问题原因很明确了,就是可变对象导致了这个问题,因此千万不要使用可变对象作为python函数/方法的默认参数, 一般建议使用None作为默认参数,即
 def foo(arg=None):
pass
												

使用可变对象作为python函数默认参数引发的问题的更多相关文章

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

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

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

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

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

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

  4. python函数默认参数陷阱

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

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

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

  6. Python 函数(默认参数)

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

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

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

  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. 头部——MimeHeaders

    http协议的请求头部更像一个键值对,例如Content-Length : 123,前面为键后面为值,表示文本长度为123.对于若干个头部在请求对象中被封装成MimeHeaders对象,MimeHea ...

  2. Cocos2D中使用CCDrawNode绘制几何图形崩溃的解决

    在cocos2D v3.x中已经不能像在v2.x中那样直接调用ccDrawXXX函数来绘制几何图形了. 我们可以使用CCDrawNode或者CCRenderer来绘制图形. 但是官方的Api手册中说的 ...

  3. TCP的流量控制与拥塞控制小结

    概述 为了提高信道的利用率TCP协议不使用停止等待协议,而是使用连续ARQ协议,意思就是可以连续发出若干个分组然后等待确认,而不是发送一个分组就停止并等待该分组的确认.其中TCP的流量控制与拥塞控制是 ...

  4. MinerHtmlThread.java 爬取页面线程

    MinerHtmlThread.java 爬取页面线程 package com.iteye.injavawetrust.miner; import org.apache.commons.logging ...

  5. Linux进程实践(4) --wait避免僵尸进程

    Wait的背景 当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) 子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程 ...

  6. iOS9关键字的简单使用

    在iOS 9 苹果推出了很多关键字, 目的其实很明确, 主要就是提高开发人员的效率, 有益于程序员之间的沟通与交流, 在开发中代码更加规范! 1. nullable 与 nonnull nullabl ...

  7. android自定义listview实现header悬浮框效果

    之前在使用iOS时,看到过一种分组的View,每一组都有一个Header,在上下滑动的时候,会有一个悬浮的Header,这种体验觉得很不错,请看下图: 上图中标红的1,2,3,4四张图中,当向上滑动时 ...

  8. [WinForm]动态显示本地目录图片与悬浮窗

    加载显示: if (File.Exists(@"D:\产品图片\" + item + ".jpg")) { //需要判断是否存在图片 Image img = I ...

  9. Swift基础之UIButton

    //设置全局变量,将下面的替换即可    //var myButton = UIButton();    //系统生成的viewDidLoad()方法    override func viewDid ...

  10. 如何反编译APK?

    1.概述 一些商业的app都包含很多精美的图片还有一些比较好的配置文件,以前某师兄就说过apk把后缀改为zip,然后解压一下就可以获得很多图片资源,但是这时候你打开一下解压出来的xml资源全是乱码.通 ...