Python中设置变量作为默认值时容易遇到的错误


Posted in Python onApril 03, 2015

思考一下下面的代码片段:
 

def foo(numbers=[]):
  numbers.append(9)
  print numbers

在这里,我们定义了一个 list (默认为空),给它加入9并且打印出来。
 

>>> foo()
[9]
>>> foo(numbers=[1,2])
[1, 2, 9]
>>> foo(numbers=[1,2,3])
[1, 2, 3, 9]

看起来还行吧?可是当我们不输入number 参数来调用 foo 函数时,神奇的事情发生了:
 

>>> foo() # first time, like before
[9]
>>> foo() # second time
[9, 9]
>>> foo() # third time...
[9, 9, 9]
>>> foo() # WHAT IS THIS BLACK MAGIC?!
[9, 9, 9, 9]

那么,这是神马情况?直觉告诉我们无论我们不输入 number 参数调用 foo 函数多少次,这里的9应该被分配进了一个空的 list。这是错的!在Python里,函数的默认值实在函数定义的时候实例化的,而不是在调用的时候。

那么我们仍然会问,为什么在调用函数的时候这个默认值却被赋予了不同的值?因为在你每次给函数指定一个默认值的时候,Python都会存储这个值。如果在调用函数的时候重写了默认值,那么这个存储的值就不会被使用。当你不重写默认值的时候,那么Python就会让默认值引用存储的值(这个例子里的numbers)。它并不是将存储的值拷贝来为这个变量赋值。这个概念可能对初学者来说,理解起来会比较吃力,所以可以这样来理解:有两个变量,一个是内部的,一个是当前运行时的变量。现实就是我们有两个变量来用相同的值进行交互,所以一旦 numbers 的值发生变化,也会改变Python里面保存的初始值的记录。

那么解决方案如下:
 

def foo(numbers=None):
  if numbers is None:
    numbers = []
  numbers.append(9)
  print numbers

通常,当人们听到这里,大家会问另一个关于默认值的问题。思考下面的程序:
 

def foo(count=0):
  count += 1
  print count

当我们运行它的时候,其结果完全是我们期望的:
 

>>> foo()
1
>>> foo()
1
>>> foo(2)
3
>>> foo(3)
4
>>> foo()
1

这又是为啥呢?其秘密不在与默认值被赋值的时候,而是这个默认值本身。整型是一种不可变的变量。跟 list 类型不同,在函数执行的过程中,整型变量是不能被改变的。当我们执行 count+=1 这句话时,我们并没有改变 count 这个变量原有的值。而是让 count 指向了不同的值。可是,当我们执行 numbers.append(9) 的时候,我们改变了原有的 list 。因而导致了这种结果。

下面是在函数里使用默认值时会碰到的另一种相同问题:
 

def print_now(now=time.time()):
  print now

跟前面一样,time.time() 的值是可变的,那么它只会在函数定义的时候计算,所以无论调用多少次,都会返回相同的时间 — 这里输出的时间是程序被Python解释运行的时间。

>>> print_now()
1373121487.91
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91

* 这个问题和它的解决方案在 Python 2.x 和 3.x 里都是类似的,在Python 3.x 里面唯一的不同,是里面的print 表达式应该是函数调用的方式(print(numbers))。

Python 相关文章推荐
Python中的zipfile模块使用详解
Jun 25 Python
python实现八大排序算法(2)
Sep 14 Python
在python中使用正则表达式查找可嵌套字符串组
Oct 24 Python
python连接mongodb密码认证实例
Oct 16 Python
python pandas实现excel转为html格式的方法
Oct 23 Python
python 与服务器的共享文件夹交互方法
Dec 27 Python
python批量修改ssh密码的实现
Aug 08 Python
利用python list完成最简单的DB连接池方法
Aug 09 Python
在python中利用pycharm自定义代码块教程(三步搞定)
Apr 15 Python
Python selenium爬取微博数据代码实例
May 22 Python
python中sys模块是做什么用的
Aug 16 Python
Python urlopen()参数代码示例解析
Dec 10 Python
用Python编写一个简单的Lisp解释器的教程
Apr 03 #Python
举例讲解Python中is和id的用法
Apr 03 #Python
详解Python2.x中对Unicode编码的使用
Apr 03 #Python
对于Python中线程问题的简单讲解
Apr 03 #Python
python BeautifulSoup设置页面编码的方法
Apr 03 #Python
用Python编写一个简单的FUSE文件系统的教程
Apr 02 #Python
用Python中的__slots__缓存资源以节省内存开销的方法
Apr 02 #Python
You might like
PHP的垃圾回收机制代码实例讲解
2021/02/27 PHP
javascript中的location用法简单介绍
2007/03/07 Javascript
读jQuery之八 包装事件对象
2011/06/21 Javascript
JS将光标聚焦在文本最后的实现代码
2014/03/28 Javascript
JS+CSS实现大气清新的滑动菜单效果代码
2015/10/22 Javascript
JS获取文件大小方法小结
2015/12/08 Javascript
七个不允许错过的jQuery小技巧
2015/12/21 Javascript
KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机的方法
2016/10/05 Javascript
微信小程序 wxapp视图容器 view详解
2016/10/31 Javascript
完美解决jQuery的hover事件在IE中不停闪动的问题
2017/02/10 Javascript
Node.js 中使用 async 函数的方法
2017/11/20 Javascript
在vue中通过axios异步使用echarts的方法
2018/01/13 Javascript
JS设计模式之状态模式概念与用法分析
2018/02/05 Javascript
vue2.0实现音乐/视频播放进度条组件
2018/06/06 Javascript
谈谈为什么你的 JavaScript 代码如此冗长
2019/01/30 Javascript
微信小程序把百度地图坐标转换成腾讯地图坐标过程详解
2019/07/10 Javascript
微信小程序实现蒙版弹出窗功能
2019/09/17 Javascript
浅谈Vue使用Cascader级联选择器数据回显中的坑
2020/10/31 Javascript
Nodejs 数组的队列以及forEach的应用详解
2021/02/25 NodeJs
[45:32]Liquid vs LGD 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
[36:41]完美世界DOTA2联赛循环赛FTD vs Magma第一场 10月30日
2020/10/31 DOTA
[01:05:29]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster BO3 第二场 1月24日
2021/03/11 DOTA
Python中getattr函数和hasattr函数作用详解
2016/06/14 Python
利用python画一颗心的方法示例
2017/01/31 Python
python Socket之客户端和服务端握手详解
2017/09/18 Python
Python实现连接postgresql数据库的方法分析
2017/12/27 Python
浅谈使用Python内置函数getattr实现分发模式
2018/01/22 Python
解决PyCharm同目录下导入模块会报错的问题
2018/10/13 Python
Python字典的基本用法实例分析【创建、增加、获取、修改、删除】
2019/03/05 Python
pyinstaller打包多个py文件和去除cmd黑框的方法
2019/06/21 Python
普通院校学生的自荐信
2013/11/27 职场文书
公司委托书范本5篇
2014/09/20 职场文书
物业工程部经理岗位职责
2015/04/09 职场文书
小学生节水倡议书
2015/04/29 职场文书
学校元旦晚会开场白
2015/05/29 职场文书
Golang生成Excel文档的方法步骤
2021/06/09 Golang