详细介绍Python函数中的默认参数


Posted in Python onMarch 30, 2015
import datetime as dt
 
def log_time(message, time=None):
  if time is None:
    time=dt.datetime.now()
  print("{0}: {1}".format(time.isoformat(), message))

最近我在一段Python代码中发现了一个因为错误的使用默认参数而产生的非常恶心的bug。如果您已经知道关于默认参数的全部内容了,只是想嘲笑一下我这可笑的错误,请直接跳到本文末尾。哎,这段代码是我写的,但是我非常确定那天我被恶魔附体了。你懂的,有时候就是这样。

本文仅仅是总结一下关于Python函数的标准参数和默认参数的一些基本内容。提醒你注意你的代码中可能存在的陷阱。如果你刚开始接触Python,开始写一些函数,我真心推荐你看一下Python官方手册中关于函数的内容,链接如下:Defining Functions 以及 More on Defining Functions。
简单复习一下函数

Python是一个强大的面向对象语言,它把这种编程范式推向了顶峰。但是,面向对象编程仍然需要依靠函数这一概念,你可以用它来处理数据。Python对于可调用对象有一个更宽泛的概念,即任何对象都可以被调用,调用的意思是对其应用数据。

函数在Python中是可调用对象,并且乍一看,它和其他语言中的函数有着类似的行为。它们获取一些数据,这些数据被称为参数,然后处理它们,接着返回结果(如果没有return语句则是None)

参数被声明为占位符(在定义函数的时候),用以代表那些当函数调用时被实际传入的对象。在Python中你不需要声明参数的类型(例如,像你在C或Java中做的那样)因为Python哲学依赖于多态。

记住,Python的变量是引用,即实际变量的内存地址。这意味着Python的函数永远以“传址”的方式工作(这里使用了一个C/C++术语),当你调用一个函数的时候,并不是复制了一份参数的值来替换占位符,而是把占位符指向了变量本身。这导致了一个非常重要的结果:你可以在函数内部改变这个变量的值。这里有一个很好可视化讲解,关于引用机制。

引用在Python扮演着非常重要的角色,它是Python完全多态方式的骨干。关于这个非常重要的主题,请点击这个链接 查看更好的解释。

为了检查你是否理解了这门语言的这一基本特性,请跟随这段简单的代码(变量ph代表的是“占位符(placeholder)”)
 

>>> def print_id(ph):
... print(hex(id(ph)))
...
>>> a = 5
>>> print(hex(id(a)))
0x84ab460
>>> print_id(a)
0x84ab460
>>>
>>> def alter_value(ph):
... ph = ph + 1
... return ph
...
>>> b = alter_value(a)
>>> b
6
>>> a
5
>>> hex(id(a))
'0x84ab460'
>>> hex(id(b))
'0x84ab470'
>>>
>>> def alter_value(ph):
... ph.append(1)
... return ph
...
>>> a = [1,2,3]
>>> b = alter_value(a)
>>> a
[1, 2, 3, 1]
>>> b
[1, 2, 3, 1]
>>> hex(id(a))
'0xb701f72c'
>>> hex(id(b))
'0xb701f72c'
>>>

如果你对这里发生的事情并不感到吃惊,那说明你已经掌握了Python中最为重要的部分之一,你可以放心的跳过下面的解释了。

print_id()函数显示,函数内部的占位符同运行时传入的变量完全一样(它们的内存地址一致)。

两个版本的alter_value()意在改变传入参数的值。正如你所看到的,第一个alter_value() 并没有像第二个alter_value()一样成功的改变变量a的值。这是为什么呢?实际上两者的行为是一样的,都是尝试修改传入的原始变量的值,但是在Python中,有些变量是不可变的(immutable),整数就在此列。另一方面,列表并不是不可变的,所以函数得以完成它的名字所保证的工作。 在这里,你可以找到关于不可变类型的更加详细的介绍 。

关于Python中的函数,还有一些要说的,但是这些是关于标准的参数的基本知识。
默认参数值

有时候你需要定义一个函数,让它接受一个参数,而且在这个参数出现或不出现时,函数有不同的行为。如果一门语言不支持这种情况,你就只有两个选择:第一种是定义两个不同的函数,决定每次调用应该选择调用哪个,第二种是 两种方法都是可行的,但是都不是最佳的。

Python和其他语言一样,支持默认参数值,即函数参数可以是调用时指定的,也可以留空,自动接受一个预定义的值。

一个关于默认值的非常简单(也很没用)的例子如下:
 

def log(message=None):
  if message:
    print("LOG: {0}".format(message))

这个函数可以带一个参数运行(可以是None)

>>> log("File closed")
LOG: File closed
>>> log(None)
>>>

但是同样也可以不带参数运行,这种情况下它会接受一个函数原型中设置的默认值(本例中是None)
 

>>> log()
>>>

你可以在标准库中找到更多有趣的例子,比如在open()函数中(请查看官方文档)
 

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

函数原型可以证明,例如 f = open('/etc/hosts')这样的调用,通过传入默认值隐藏了很多参数 (mode, buffering, encoding, 等),并且使这个函数的典型应用案例变得非常简单易用。

正如你在内建的open()函数中看到的那样,我们可以在函数中使用标准或者默认参数,但是两者在函数中出现的次序是固定的:首先调用标准参数,然后调用默认参数。
 

def a_rich_function(a, b, c, d=None, e=0):
  pass

原因是显而易见的:如果我们可以在标准参数前面放置一个默认参数,语言就无法理解,默认参数是否已经被初始化。例如,考虑下面这个函数定义
 

def a_rich_function(a, b, d=None, c, e=0):
  pass

当调用函数a_rich_function(1, 2, 4, 5)时,我们传入了什么参数? 是d=4, c=5 还是c=4, e=5?因为d有一个默认的值。因此这种顺序的定义是被禁止的,如果你这样做,Python会抛出一个SyntaxError
 

>>> def a_rich_function(a, b, d=None, c, e=0):
... pass
...
 File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
>>>

默认参数求值

默认参数可以通过普通值或是函数调用结果来提高,但是后者这种技术需要一个特别的警示

一个普通的值是硬编码的,因此除了编译时,其他时候是不需要求值的,但是函数调用期望在运行时执行求值。所以我们可以这样写
 

import datetime as dt
 
def log_time(message, time=dt.datetime.now()):
  print("{0}: {1}".format(time.isoformat(), message))

每次我们调用log_time()时都期望它能够正确提供当前时间。悲剧的是并没有成功:默认参数在定义时求值(比如说当你首次导入模块时),调用的结果如下
 

>>> log_time("message 1")
2015-02-10T21:20:32.998647: message 1
>>> log_time("message 2")
2015-02-10T21:20:32.998647: message 2
>>> log_time("message 3")
2015-02-10T21:20:32.998647: message 3

如果把默认值赋给一个类的实例,结果会更加奇怪,你可以在Hitchhiker's Guide to Python!中读到相关内容。根据。。通常的解决方法是把默认参数替换为None,并且在函数内部检查参数值。
 
结论

默认参数能够极大的简化API,你需要关注它唯一的“失败点”,即求值的时机。令人惊奇的是,Python最基本的内容之一,函数的参数和引用,是最大的错误源之一,有时候对于有经验的程序员也一样。我建议抽时间学习一下引用和多态。
相关阅读:

  •     OOP concepts in Python 2.x ? Part 2
  •     Python 3 OOP Part 1 ? Objects and types
  •     Digging up Django class-based views ? 2
  •     Python Generators ? From Iterators to Cooperative Multitasking ? 2
  •     OOP concepts in Python 2.x ? Part 1
Python 相关文章推荐
对于Python装饰器使用的一些建议
Jun 03 Python
Python基于PycURL自动处理cookie的方法
Jul 25 Python
Python使用回溯法子集树模板解决爬楼梯问题示例
Sep 08 Python
Python实现PS滤镜的旋转模糊功能示例
Jan 20 Python
python获取文件真实链接的方法,针对于302返回码
May 14 Python
python人民币小写转大写辅助工具
Jun 20 Python
Python 炫技操作之合并字典的七种方法
Apr 10 Python
python中的split、rsplit、splitlines用法说明
Oct 23 Python
5 分钟读懂Python 中的 Hook 钩子函数
Dec 09 Python
matplotlib绘制鼠标的十字光标的实现(内置方式)
Jan 06 Python
详解Python openpyxl库的基本应用
Feb 26 Python
进行数据处理的6个 Python 代码块分享
Apr 06 Python
在Python中利用Into包整洁地进行数据迁移的教程
Mar 30 #Python
在Linux上安装Python的Flask框架和创建第一个app实例的教程
Mar 30 #Python
使用Python中PDB模块中的命令来调试Python代码的教程
Mar 30 #Python
深入讨论Python函数的参数的默认值所引发的问题的原因
Mar 30 #Python
使用Python标准库中的wave模块绘制乐谱的简单教程
Mar 30 #Python
Python中使用语句导入模块或包的机制研究
Mar 30 #Python
优化Python代码使其加快作用域内的查找
Mar 30 #Python
You might like
php生成excel文件的简单方法
2014/02/08 PHP
php实现mysql备份恢复分卷处理的方法
2014/12/26 PHP
DEDECMS首页调用图片集里的多张图片
2015/06/05 PHP
PHP实现二维数组按照指定的字段进行排序算法示例
2019/04/23 PHP
用js脚本控制asp.net下treeview的NodeCheck的实现代码
2010/03/02 Javascript
深入了解Node.js中的一些特性
2014/09/25 Javascript
nw.js实现类似微信的聊天软件
2015/03/16 Javascript
使用JavaScript实现旋转的彩圈特效
2015/06/23 Javascript
js中window.open的参数及注意注意事项
2016/07/06 Javascript
jQuery 检查某个元素在页面上是否存在实例代码
2016/10/27 Javascript
最细致的vue.js基础语法 值得收藏!
2016/11/03 Javascript
nodejs对express中next函数的一些理解
2017/09/08 NodeJs
vue+Element实现搜索关键字高亮功能
2019/05/28 Javascript
jquery中attr、prop、data区别与用法分析
2019/09/25 jQuery
[01:02:06]LGD vs Mineski Supermajor 胜者组 BO3 第二场 6.5
2018/06/06 DOTA
python写的一个文本编辑器
2014/01/23 Python
python解决pandas处理缺失值为空字符串的问题
2018/04/08 Python
无法使用pip命令安装python第三方库的原因及解决方法
2018/06/12 Python
深入浅析Python获取对象信息的函数type()、isinstance()、dir()
2018/09/17 Python
Python实现针对json中某个关键字段进行排序操作示例
2018/12/25 Python
解决Python中报错TypeError: must be str, not bytes问题
2020/04/07 Python
jupyter notebook 使用过程中python莫名崩溃的原因及解决方式
2020/04/10 Python
解决Tensorflow2.0 tf.keras.Model.load_weights() 报错处理问题
2020/06/12 Python
使用canvas来完成线性渐变和径向渐变的功能的方法示例
2019/07/25 HTML / CSS
深深扎根运动世界的生活品牌:Tillys
2017/10/30 全球购物
Myprotein俄罗斯官网:欧洲第一运动营养品牌
2019/05/05 全球购物
介绍一下Linux内核的排队自旋锁
2014/01/04 面试题
应届毕业生个人自我评价
2013/09/20 职场文书
电子商务应届生求职信
2013/11/16 职场文书
安全标准化汇报材料
2014/02/03 职场文书
幼师求职自荐信
2015/03/26 职场文书
2015年工程部工作总结
2015/04/30 职场文书
学校教学管理制度
2015/08/06 职场文书
导游词之秦皇岛燕塞湖
2020/01/03 职场文书
MySQL修炼之联结与集合浅析
2021/10/05 MySQL
【js设计模式】SOLID五大设计原则
2022/03/24 Javascript