详细介绍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正常时间和unix时间戳相互转换的方法
Apr 23 Python
Python 实现12306登录功能实例代码
Feb 09 Python
Python SQL查询并生成json文件操作示例
Aug 17 Python
Python中文件的写入读取以及附加文字方法
Jan 23 Python
Python实现蒙特卡洛算法小实验过程详解
Jul 12 Python
python实现的config文件读写功能示例
Sep 24 Python
pytorch之Resize()函数具体使用详解
Feb 27 Python
Django 后台带有字典的列表数据与页面js交互实例
Apr 03 Python
Jupyter notebook 启动闪退问题的解决
Apr 13 Python
基于opencv的selenium滑动验证码的实现
Jul 24 Python
Python 数据的累加与统计的示例代码
Aug 03 Python
Python pandas之求和运算和非空值个数统计
Aug 07 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
乱谈我对耳机、音箱的感受
2021/03/02 无线电
php自动注册登录验证机制实现代码
2011/12/20 PHP
用来解析.htgroup文件的PHP类
2012/09/05 PHP
PHP 文件编程综合案例-文件上传的实现
2013/07/03 PHP
php常用文件操作函数汇总
2014/11/22 PHP
thinkphp配置连接数据库技巧
2014/12/02 PHP
PHP里的$_GET数组介绍
2019/03/22 PHP
因str_replace导致的注入问题总结
2019/08/08 PHP
实例讲解PHP表单
2020/06/10 PHP
修改jQuery Validation里默认的验证方法
2012/02/14 Javascript
js函数返回多个返回值的示例代码
2013/11/05 Javascript
jquery $(document).ready()和window.onload的区别浅析
2015/02/04 Javascript
Bootstrap树形组件jqTree的简单封装
2016/01/25 Javascript
JS获取checkbox的个数简单实例
2016/08/19 Javascript
Angular4实现动态添加删除表单输入框功能
2017/08/11 Javascript
react实现菜单权限控制的方法
2017/12/11 Javascript
electron demo项目npm install安装失败的解决方法
2018/02/06 Javascript
Element-ui之ElScrollBar组件滚动条的使用方法
2018/09/14 Javascript
在Angular中实现一个级联效果的下拉框的示例代码
2020/05/20 Javascript
vant中的toast层级改变操作
2020/11/04 Javascript
python两种遍历字典(dict)的方法比较
2014/05/29 Python
Python发送Email方法实例
2014/08/21 Python
python实现数值积分的Simpson方法实例分析
2015/06/05 Python
Python使用matplotlib绘制多个图形单独显示的方法示例
2018/03/14 Python
使用python将mysql数据库的数据转换为json数据的方法
2019/07/01 Python
详解PANDAS 数据合并与重塑(join/merge篇)
2019/07/09 Python
MANGO官方网站:西班牙芒果服装品牌
2017/01/15 全球购物
菲律宾酒店预订网站:Hotels.com菲律宾
2017/07/12 全球购物
美国在线乐器和设备商店:Musician’s Friend
2018/07/06 全球购物
经典优秀毕业生求职信范文分享
2013/12/18 职场文书
给校长的建议书100字
2014/05/16 职场文书
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
2015年银行工作总结范文
2015/04/01 职场文书
接收函
2019/04/22 职场文书
《成长的天空》读后感3篇
2019/12/06 职场文书
简述Java中throw-throws异常抛出
2021/08/07 Java/Android