写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. Android初级教程人品计算器

    先看布局: main_activity.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/andr ...

  2. 物料事务处理interface与temp解析

    MTL_TRANSACTIONS_INTERFACE MTL_TRANSACTIONS_INTERFACE is the interface point between non– Inventory ...

  3. Ext JS 6应用程序Build后出现“c is not a constructor return new c(a[0])”的处理

    概述 在对Ext JS 6的应用程序打包后,时不时会出现以下错误: 由于是压缩后出现的错误,要进行调试也无从下手,因而这个错误会令新手手足无措,不知道是怎么回事. 错误原因 造成该错误的主要原因是要创 ...

  4. Java进阶(二)文件读操作

    本文以实际的读取文件为例子,介绍流的概念,以及输入流的基本使用. 按照前面介绍的知识,将文件中的数据读入程序,是将程序外部的数据传入程序中,应该使用输入流--InputStream或Reader.而由 ...

  5. Linux的资源管理器

    说是资源管理器,其实就是使用命令来对Linux运行系统的参数的查看.下面就一起看一看怎么像在windows下查看资源管理器吧. 1.查看进程(额,自然是电脑上正在运行的进程咯) ps aux 其中a ...

  6. 小强的HTML5移动开发之路(17)——HTML5内联SVG

    来自:http://blog.csdn.net/dawanganban/article/details/18189181 一.什么是SVG 可缩放矢量图形是基于可扩展标记语言(标准通用标记语言的子集) ...

  7. Linux Android 多点触摸协议 原文出自【比特网】,转载请保留原文链接:http://soft.chinabyte.com/os/71/12306571.shtml

    为了使用功能强大的多点触控设备,就需要一种方案去上报用户层所需的详细的手指触摸数据.这个文档所描述的多点触控协议可以让内核驱动程序向用户层上报任意多指的数据信息. 使用说明 单点触摸信息是以ABS承载 ...

  8. MySQL学习笔记_7_MySQL常用内置函数

    MySQL常用内置函数 说明: 1)可以用在SELECT/UPDATE/DELETE中,及where,orderby,having中 2)在函数里将字段名作为参数,变量的值就是字段所对应的每一行的值. ...

  9. 彻底搞清楚 RxJava 是什么东西

    其实从rxjava14年出现到现在,我是去年从一个朋友那里听到的,特别是随着现在app项目越来越大,分层越来越不明确的情况下,rxjava出现了,以至于出现了rxandroid.其实如果你了解观察者模 ...

  10. Helix Streaming Server 简单配置

    双击桌面上新出现的"HelixServer"图标,正常的话应该如图9,不要关闭这个窗口. 双击"HelixServerAdministrator"图标,输入用户 ...