写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. 后端分布式系列:分布式存储-HDFS NameNode 设计实现解析

    接前文 分布式存储-HDFS 架构解析,我们总体分析了 HDFS 架构的主要构成组件包括:NameNode.DataNode 和 Client.本文首先进一步解析 HDFS NameNode 的设计和 ...

  2. 关于新版本,iOS10的相关内容,兼容iOS 10 资料整理笔记

    1.Notification(通知) 自从Notification被引入之后,苹果就不断的更新优化,但这些更新优化只是小打小闹,直至现在iOS 10开始真正的进行大改重构,这让开发者也体会到UserN ...

  3. J2EE进阶(十二)SSH框架整合常见问题汇总(三)

    在挂失用户时,发现userid值为空,但是在前台输入处理账号22时,通过后台输出可以看出,后台根据前端输入在数据库中查询到结果对象并输出该对象的userid,而且Guashi对象也获取到了其值. 解决 ...

  4. iOS中 陀螺仪/加速器 韩俊强的博客

    引进框架: #import <CoreMotion/CoreMotion.h> 定义属性初始化相关: #import "ViewController.h" #impor ...

  5. 连接器与容器的桥梁——CoyoteAdapter

    如果把整个tomcat内核最高抽象程度模块化,可以看成是由连接器Connector和容器Container组成,连接器负责HTTP请求接收及响应,生成请求对象及响应对象并交由容器处理,而容器则根据请求 ...

  6. Java 8新特性探究(五)Base64详解

    BASE64 编码是一种常用的字符编码,在很多地方都会用到.但base64不是安全领域下的加密解密算法.能起到安全作用的效果很差,而且很容易破解,他核心作用应该是传输数据的正确性,有些网关或系统只能使 ...

  7. 代理服务器 详解 Apache与Nginx的比较与分析

    正向代理:是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容 ...

  8. 【leetcode75】Intersection of Two Arrays(数组的交集)

    题目描述: 给定两个数组求他们的公共部分,输出形式是数组,相同的元素只是输出一次 例如: nums1 = [1, 2, 2, 1], nums2 = [2, 2], return [2]. 原文描述: ...

  9. JSP编译成Servlet(五)JDT Compiler编译器

    通过JSP编译器编译后生成了对应的java文件,接下去要把Java文件编译成class文件.对于这部分完全没有必要重新造轮子,常见的优秀编译工具有Eclipse JDT Java编译器和Ant编译器. ...

  10. Android studio使用git-android学习之旅(79)

    首先我参考了hello_my_show和梦痕_sky的博客,表示感谢 android studio对于git的支持是很好的,这节课我们拉讲解怎么使用git可视化工具来clone project和提交修 ...