详细介绍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中的文本处理
Apr 11 Python
Python中用Decorator来简化元编程的教程
Apr 13 Python
python中map、any、all函数用法分析
Apr 21 Python
Python写入CSV文件的方法
Jul 08 Python
python中redis查看剩余过期时间及用正则通配符批量删除key的方法
Jul 30 Python
Python函数返回不定数量的值方法
Jan 22 Python
Python爬虫使用浏览器cookies:browsercookie过程解析
Oct 22 Python
利用Python的sympy包求解一元三次方程示例
Nov 22 Python
Python如何操作docker redis过程解析
Aug 10 Python
Pycharm Available Package无法显示/安装包的问题Error Loading Package List解决
Sep 18 Python
python如何利用paramiko执行服务器命令
Nov 07 Python
Python __slots__的使用方法
Nov 15 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 正则表达式的学习探讨
2013/06/06 PHP
php判断数组元素中是否存在某个字符串的方法
2014/06/14 PHP
php+ajax实现商品对比功能示例
2019/04/13 PHP
网上抓的一个特效
2007/05/11 Javascript
写出更好的JavaScript之undefined篇(上)
2009/11/22 Javascript
js模仿jquery的写法示例代码
2013/06/16 Javascript
jQuery父级以及同级元素查找介绍
2013/09/04 Javascript
js setTimeout()函数介绍及应用以倒计时为例
2013/12/12 Javascript
js控制table合并具体实现
2014/02/20 Javascript
推荐10个2014年最佳的jQuery视频插件
2014/11/12 Javascript
jQuery提示效果代码分享
2014/11/20 Javascript
jQuery中:first选择器用法实例
2014/12/30 Javascript
AngularJS通过$location获取及改变当前页面的URL
2016/09/23 Javascript
利用angular.copy取消变量的双向绑定与解析
2016/11/25 Javascript
nodejs和php实现图片访问实时处理
2017/01/05 NodeJs
Bootstrap 表单验证formValidation 实现远程验证功能
2017/05/17 Javascript
妙用缓存调用链实现JS方法的重载
2018/04/30 Javascript
vue使用中的内存泄漏【推荐】
2018/07/10 Javascript
JS实现点击拉拽轮播图pc端移动端适配
2018/09/05 Javascript
JS如何实现封装列表右滑动删除收藏按钮
2020/07/23 Javascript
Python的re模块正则表达式操作
2016/05/25 Python
Python3.5内置模块之os模块、sys模块、shutil模块用法实例分析
2019/04/27 Python
浅谈selenium如何应对网页内容需要鼠标滚动加载的问题
2020/03/14 Python
python 制作简单的音乐播放器
2020/11/25 Python
英国百安居装饰建材网上超市:B&Q
2016/09/13 全球购物
英国时尚饰品和发饰购物网站:Claire’s
2017/07/04 全球购物
写自荐信的注意事项
2014/03/09 职场文书
少先队学雷锋活动总结范文
2014/03/09 职场文书
护理人员的自我评价分享
2014/03/15 职场文书
微笑服务标语
2014/06/24 职场文书
以幸福为主题的活动方案
2014/08/22 职场文书
2015年工程师工作总结
2015/04/30 职场文书
行政介绍信范文
2015/05/04 职场文书
Java中多线程下载图片并压缩能提高效率吗
2021/07/01 Java/Android
Java SSM配置文件案例详解
2021/08/30 Java/Android
Python 多线程处理任务实例
2021/11/07 Python