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中使用PyHook监听鼠标和键盘事件实例
Jul 18 Python
python PIL模块与随机生成中文验证码
Feb 27 Python
Python复制Word内容并使用格式设字体与大小实例代码
Jan 22 Python
python最小生成树kruskal与prim算法详解
Jan 17 Python
使用Python paramiko模块利用多线程实现ssh并发执行操作
Dec 05 Python
pandas的相关系数与协方差实例
Dec 27 Python
django restframework serializer 增加自定义字段操作
Jul 15 Python
Python 日期与时间转换的方法
Aug 01 Python
python中uuid模块实例浅析
Dec 29 Python
pycharm 实现光标快速移动到括号外或行尾的操作
Feb 05 Python
python使用glob检索文件的操作
May 20 Python
python的netCDF4批量处理NC格式文件的操作方法
Mar 21 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
Ping服务的php实现方法,让网站快速被收录
2012/02/04 PHP
领悟php接口中interface存在的意义
2013/06/27 PHP
PHP文件上传处理案例分析
2016/10/15 PHP
PHP实现添加购物车功能
2017/03/06 PHP
JavaScript 中的事件教程
2007/04/05 Javascript
初学JavaScript第二章
2008/09/30 Javascript
jquery 学习笔记 传智博客佟老师附详细注释
2020/09/12 Javascript
JS的Document属性和方法小结
2013/09/17 Javascript
JS实现让网页背景图片斜向移动的方法
2015/02/25 Javascript
jQuery网页版打砖块小游戏源码分享
2015/08/20 Javascript
基于javascript实现listbox左右移动
2016/01/29 Javascript
使用JS实现图片展示瀑布流效果(简单实例)
2016/09/06 Javascript
javascript的几种写法总结
2016/09/30 Javascript
jQuery插件FusionCharts实现的MSBar3D图效果示例【附demo源码】
2017/03/23 jQuery
详解Vue路由开启keep-alive时的注意点
2017/06/20 Javascript
vue axios请求超时的正确处理方法
2018/04/02 Javascript
vue addRoutes实现动态权限路由菜单的示例
2018/05/15 Javascript
vue用递归组件写树形控件的实例代码
2018/07/19 Javascript
vue中进行微博分享的实例讲解
2019/10/14 Javascript
Vue学习笔记之计算属性与侦听器用法
2019/12/07 Javascript
[06:40]2014DOTA2西雅图国际邀请赛 DK战队巡礼
2014/07/07 DOTA
Python中logging模块的用法实例
2014/09/29 Python
python搜索指定目录的方法
2015/04/29 Python
在Python的Django框架中更新数据库数据的方法
2015/07/17 Python
创建pycharm的自定义python模板方法
2018/05/23 Python
python实现AES和RSA加解密的方法
2019/03/28 Python
pycharm无法安装第三方库的问题及解决方法以scrapy为例(图解)
2020/05/09 Python
域名注册、建站工具、网页主机、SSL证书:Dynadot
2017/01/06 全球购物
WEB控件可以激发服务端事件,请谈谈服务端事件是怎么发生并解释其原理?自动传回是什么?为什么要使用自动传回?
2012/02/21 面试题
安全员岗位职责
2013/11/11 职场文书
追悼会上的答谢词
2014/01/10 职场文书
幼儿园中秋节活动方案
2014/02/06 职场文书
遥感技术与仪器求职信
2014/02/22 职场文书
公司节能减排倡议书
2014/05/14 职场文书
教师专业自荐信
2014/05/31 职场文书
不尊敬老师的检讨书
2014/12/21 职场文书