详细介绍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实现百度关键词排名查询
Mar 30 Python
在Python中使用NLTK库实现对词干的提取的教程
Apr 08 Python
python访问类中docstring注释的实现方法
May 04 Python
python difflib模块示例讲解
Sep 13 Python
python获取磁盘号下盘符步骤详解
Jun 19 Python
Python实现基于SVM的分类器的方法
Jul 19 Python
简单瞅瞅Python vars()内置函数的实现
Sep 27 Python
python3-flask-3将信息写入日志的实操方法
Nov 12 Python
Python 脚本的三种执行方式小结
Dec 21 Python
Python 解析pymysql模块操作数据库的方法
Feb 18 Python
python修改linux中文件(文件夹)的权限属性操作
Mar 05 Python
Python 通过正则表达式快速获取电影的下载地址
Aug 17 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/07/01 PHP
在Laravel中使用GuzzleHttp调用第三方服务的API接口代码
2019/10/15 PHP
JavaScript中的闭包原理分析
2010/03/08 Javascript
javascript定时保存表单数据的代码
2011/03/17 Javascript
jquery事件机制扩展插件 jquery鼠标右键事件
2011/12/21 Javascript
js菜单点击显示或隐藏效果的简单实例
2014/01/13 Javascript
jq实现酷炫的鼠标经过图片翻滚效果
2014/03/12 Javascript
js语法学习之判断一个对象是否为数组
2014/05/13 Javascript
JS实现网页表格自动变大缩小的方法
2015/03/09 Javascript
浅谈jquery中delegate()与live()
2015/06/22 Javascript
javascript点击按钮实现隐藏显示切换效果
2016/02/03 Javascript
JS在Chrome浏览器中showModalDialog函数返回值为undefined的解决方法
2016/08/03 Javascript
jQuery删除当前节点元素
2016/12/07 Javascript
jQuery中的siblings()是什么意思(推荐)
2016/12/29 Javascript
SpringMVC+bootstrap table实例详解
2017/06/02 Javascript
解决vue2 在mounted函数无法获取prop中的变量问题
2018/11/15 Javascript
如何为你的JavaScript代码日志着色详解
2019/04/08 Javascript
Vue数据绑定简析小结
2019/05/07 Javascript
vue中对象数组去重的实现
2020/02/06 Javascript
Python实现Const详解
2015/01/27 Python
PyCharm 常用快捷键和设置方法
2017/12/20 Python
python MysqlDb模块安装及其使用详解
2018/02/23 Python
Python-ElasticSearch搜索查询的讲解
2019/02/25 Python
python操作文件的参数整理
2019/06/11 Python
python Opencv计算图像相似度过程解析
2019/12/03 Python
使用python-opencv读取视频,计算视频总帧数及FPS的实现
2019/12/10 Python
html5利用canvas绘画二级树形结构图的示例
2017/09/27 HTML / CSS
Elemis美国官网:英国的第一豪华护肤品牌
2018/03/15 全球购物
求最大连续递增数字串(如"ads3sl456789DF3456ld345AA"中的"456789")
2015/09/11 面试题
递归计算如下递归函数的值(斐波拉契)
2012/02/04 面试题
生产厂厂长岗位职责
2013/12/25 职场文书
三查三看党性分析材料
2014/02/18 职场文书
反邪教宣传工作方案
2014/05/07 职场文书
创先争优活动心得体会
2014/09/04 职场文书
2014年营销工作总结
2014/11/22 职场文书
2015年教育实习工作总结
2015/04/24 职场文书