详细介绍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命令行交互提示符的方法
Jan 14 Python
深入解析Python的Tornado框架中内置的模板引擎
Jul 11 Python
对python 多线程中的守护线程与join的用法详解
Feb 18 Python
Django Python 获取请求头信息Content-Range的方法
Aug 06 Python
vim自动补全插件YouCompleteMe(YCM)安装过程解析
Oct 21 Python
Python爬取新型冠状病毒“谣言”新闻进行数据分析
Feb 16 Python
Python识别html主要文本框过程解析
Feb 18 Python
如何在Python 游戏中模拟引力
Mar 27 Python
Python中flatten( ),matrix.A用法说明
Jul 05 Python
通过Python实现Payload分离免杀过程详解
Jul 13 Python
Python性能分析工具py-spy原理用法解析
Jul 27 Python
Python实现灰色关联分析与结果可视化的详细代码
Mar 25 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实现的生成静态HTML速度快类库
2007/03/31 PHP
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 2611816 bytes)
2014/11/08 PHP
PHP合并数组函数array_merge用法分析
2017/02/17 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
PHP设计模式(五)适配器模式Adapter实例详解【结构型】
2020/05/02 PHP
js getElementsByTagName的简写方式
2010/06/27 Javascript
JS文本框追加多个下拉框的值的简单实例
2013/07/12 Javascript
javascript获取所有同类checkbox选项(实例代码)
2013/11/07 Javascript
在页面加载完成后通过jquery给多个span赋值
2014/05/21 Javascript
js实现类似于add(1)(2)(3)调用方式的方法
2015/03/04 Javascript
整理JavaScript创建对象的八种方法
2015/11/03 Javascript
分享一些常用的jQuery动画事件和动画函数
2015/11/27 Javascript
jquery获取select选中值的方法分析
2015/12/22 Javascript
一些实用性较高的js方法
2016/04/19 Javascript
Node.js刷新session过期时间的实现方法推荐
2016/05/18 Javascript
深入理解setTimeout函数和setInterval函数
2016/05/20 Javascript
Node.js安装配置图文教程
2017/05/10 Javascript
JavaScript基础之this详解
2017/06/04 Javascript
JS获取鼠标坐标并且根据鼠标位置不同弹出不同内容
2017/06/12 Javascript
Javascript实现找不同色块的游戏
2017/07/17 Javascript
web前端vue filter 过滤器
2018/01/12 Javascript
webpack之devtool详解
2018/02/10 Javascript
用Axios Element实现全局的请求loading的方法
2018/03/15 Javascript
Vue中Axios从远程/后台读取数据
2019/01/21 Javascript
Vue动态生成表格的行和列
2019/07/18 Javascript
微信小程序学习总结(三)条件、模板、文件引用实例分析
2020/06/04 Javascript
Python reduce函数作用及实例解析
2020/05/08 Python
Pandas直接读取sql脚本的方法
2021/01/21 Python
CSS3实现任意图片lowpoly动画效果实例
2017/05/11 HTML / CSS
英国最大的百货公司:Harrods
2016/08/18 全球购物
初三家长会邀请函
2014/01/18 职场文书
高三上学期学习自我评价
2014/04/23 职场文书
党的群众路线教育实践活动对照检查材料思想汇报
2014/09/19 职场文书
警察群众路线整改措施
2014/09/26 职场文书
学校运动会开幕词
2016/03/03 职场文书
centos8安装nginx1.9.1的详细过程
2021/08/02 Servers