深入讲解Python函数中参数的使用及默认参数的陷阱


Posted in Python onMarch 13, 2016

C++里函数可以设置缺省参数,Java不可以,只能通过重载的方式来实现,python里也可以设置默认参数,最大的好处就是降低函数难度,函数的定义只有一个,并且python是动态语言,在同一名称空间里不能有想多名称的函数,如果出现了,那么后出现的会覆盖前面的函数。

def power(x, n=2):
  s = 1
  while n > 0:
    n = n - 1
    s = s * x
  return s

看看结果:

>>> power(5)
25
>>> power(5,3)
125

注意: 必选参数在前,默认参数在后,否则Python的解释器会报错。
建议:*当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
默认参数也有坑,看看下面的代码,先定义一个list,添加一个end再返回:

def add_end(L=[]):
  L.append('END')
  return L

看看调用结果:

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

这里需要解释一下,Python函数在定义的时候,默认参数L的值就被计算出来了,即[]。此时L指向[]。所以如果L中的内容改变了,下次调用引用的内容也就不再是[]了。所以要牢记一点定义默认参数必须指向不可变对象!。

可变参数
第一种方法,传入的参数为一个list或者tuple。

def calc(numbers):
  sum = 0
  for n in numbers:
    sum = sum + n * n
  return sum

调用方式:

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

第二种方式,直接传入多个参数,函数内部会自动用一个tuple接收。

def calc(*numbers):
  sum = 0
  for n in numbers:
    sum = sum + n * n
  return sum

调用方式:

>>> calc(1, 2)
5
>>> calc()
0

这个时候如果还想把一个list或者tuple里的数据传进去,可以这样:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def person(name, age, **kw):
  print 'name:', name, 'age:', age, 'other:', kw

调用示例:

>>> person('Michael', 30)
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
递归函数
基本的也没什么可讲的,和Java/C++里一样,就是调用本身的一种。这里重点介绍一下尾递归优化。事实上尾递归和循环效果是一样的,很显然的一个优点那就是可以防止递归调用栈溢出。
定义:在函数返回的时候调用自身,并且,return语句不能包含表达式。编译器或者解释器可以对其做优化,无论调用多少次,只占用一个栈帧,不会出现溢出的情况。
举个简单的例子,以阶乘函数为例:

def fact(n):
  if n==1:
    return 1
  return n * fact(n - 1)

如果传入的n很大,就可能会溢出,这是由于return n * fact(n - 1)引入了乘法表达式,就不是尾递归了。把代码改一下:

def fact(n):
  return fact_iter(n, 1)

def fact_iter(num, product):
  if num == 1:
    return product
  return fact_iter(num - 1, num * product)

默认参数陷阱
Python的函数定义提供了默认参数这个选择,使得函数的定义和使用更加的灵活,但是也会带来一些坑,例如之前的一个例子:
函数定义:

def add_end(L=[]):
  L.append('END')
  return L

调用函数的结果:

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

很明显这个与函数的定义初衷不符,用一句话解释就是:
Default values are computed once, then re-used.
为了深入研究这个问题,我们来看看另一个例子:

# coding=utf-8

def a():
  print "a executed"
  return []

def b(x=a()):
  print "id(x):", id(x)
  x.append(5)
  print "x:", x

for i in range(2):
  print "不带参数调用,使用默认参数"
  b()
  print b.__defaults__
  print "id(b.__defaults__[0]):", id(b.__defaults__[0])

for i in range(2):
  print "带参数调用,传入一个list"
  b(list())
  print b.__defaults__
  print "id(b.__defaults__[0]):", id(b.__defaults__[0])

NOTE:稍微解释一下,所有默认值都存储在函数对象的__defaults__属性中,这是一个列表,每一个元素均为一个默认参数值。
来看看输出结果:

a executed
不带参数调用,使用默认参数
id(x): 140038854650552
x: [5]
([5],)
id(b.__defaults__[0]): 140038854650552
不带参数调用,使用默认参数
id(x): 140038854650552
x: [5, 5]
([5, 5],)
id(b.__defaults__[0]): 140038854650552
带参数调用,传入一个list
id(x): 140038854732400
x: [5]
([5, 5],)
id(b.__defaults__[0]): 140038854650552
带参数调用,传入一个list
id(x): 140038854732472
x: [5]
([5, 5],)
id(b.__defaults__[0]): 140038854650552

简单分析一下输出结果:
第1行
在定义函数b(),即执行def语句,代码第7行def b(x=a()):的时候,这句话使用了默认参数,所以在定义的时候会计算默认参数x的值,这个时候会调用a(),所以打印出了a executed。
第2~6行
第一次执行循环,代码第14行调用b()没有传递参数,使用默认参数,此时x=[],所以调用一次之后

print b.__defaults__

输出结果为

([5],)

第7~11行
第二次循环,代码第14行调用b()没有传递参数,使用默认参数。
注意:默认参数只会计算一次,也就是说那个内存区域就固定了,但是这个地址所指向的是一个list,内容可以改变,此时由于上一次调用x: [5],所以
print b.__defaults__

输出结果为

([5, 5],)

第12~16行
第二个循环语句,第一次循环,代码第20行传入一个空的list,所以不使用默认参数,此时x=[],所以

print b.__defaults__

输出结果为

 

([5],)

第18~21行
第二个循环语句,第二次循环,代码第20行传入一个空的list,所以也不使用默认参数,此时仍然是x=[],所以
print b.__defaults__

输出结果依然为

 

([5],)

函数也是对象,因此定义的时候就被执行,默认参数是函数的属性,它的值可能会随着函数被调用而改变。其他对象不都是如此吗?
牢记: 默认参数必须指向不变对象!代码改一下如下:
# coding=utf-8

def a():
  print "a executed"
  return None

def b(x=a()):
  print "id(x):", id(x)
  if x is None:
    x = []
  x.append(5)
  print "x:", x

for i in range(2):
  print "不带参数调用,使用默认参数"
  b()
  print b.__defaults__
  print "id(b.__defaults__[0]):", id(b.__defaults__[0])

for i in range(2):
  print "带参数调用,传入一个list"
  b(list())
  print b.__defaults__
  print "id(b.__defaults__[0]):", id(b.__defaults__[0])

此时的输出结果看看是什么:

a executed
不带参数调用,使用默认参数
id(x): 9568656
x: [5]
(None,)
id(b.__defaults__[0]): 9568656
不带参数调用,使用默认参数
id(x): 9568656
x: [5]
(None,)
id(b.__defaults__[0]): 9568656
带参数调用,传入一个list
id(x): 140725126699632
x: [5]
(None,)
id(b.__defaults__[0]): 9568656
带参数调用,传入一个list
id(x): 140725126699704
x: [5]
(None,)
id(b.__defaults__[0]): 9568656
Python 相关文章推荐
python sqlobject(mysql)中文乱码解决方法
Nov 14 Python
python列表与元组详解实例
Nov 01 Python
Python程序设计入门(1)基本语法简介
Jun 13 Python
Python实现全角半角转换的方法
Aug 18 Python
使用C++扩展Python的功能详解
Jan 12 Python
python实现内存监控系统
Mar 07 Python
Python Django的安装配置教程图文详解
Jul 17 Python
对python3中的RE(正则表达式)-详细总结
Jul 23 Python
Python文件操作函数用法实例详解
Dec 24 Python
python 实现简单的FTP程序
Dec 27 Python
python base64库给用户名或密码加密的流程
Jan 02 Python
关于Pytorch的MNIST数据集的预处理详解
Jan 10 Python
编写Python小程序来统计测试脚本的关键字
Mar 12 #Python
使用Python内置的模块与函数进行不同进制的数的转换
Mar 12 #Python
Python语言的面相对象编程方式初步学习
Mar 12 #Python
举例讲解Python中的list列表数据结构用法
Mar 12 #Python
Python中的if、else、elif语句用法简明讲解
Mar 11 #Python
使用Python读写文本文件及编写简单的文本编辑器
Mar 11 #Python
简单讲解Python中的数字类型及基本的数学计算
Mar 11 #Python
You might like
PHP daddslashes 使用方法介绍
2012/10/26 PHP
CI框架源码阅读,系统常量文件constants.php的配置
2013/02/28 PHP
yii2 数据库读写分离配置示例
2017/02/10 PHP
PHP实现的获取文件mimes类型工具类示例
2018/04/08 PHP
使用laravel的migrate创建数据表的方法
2019/09/30 PHP
js克隆对象、数组的常用方法介绍
2013/09/26 Javascript
JS动态调用方法名示例介绍
2013/12/18 Javascript
浅析jquery的js图表组件highcharts
2014/03/06 Javascript
jQuery实现简单隔行变色的方法
2016/02/20 Javascript
JQUERY的AJAX请求缓存里的数据问题处理
2016/02/23 Javascript
Markdown与Bootstrap相结合实现图片自适应属性
2016/05/04 Javascript
js实现表单及时验证功能 用户信息立即验证
2016/09/13 Javascript
JS自定义混合Mixin函数示例
2016/11/26 Javascript
JS获取一个表单字段中多条数据并转化为json格式
2017/10/17 Javascript
Vue打包后出现一些map文件的解决方法
2018/02/13 Javascript
6行代码实现微信小程序页面返回顶部效果
2018/12/28 Javascript
微信小程序实现九宫格抽奖
2020/04/15 Javascript
Vue使用axios出现options请求方法
2019/05/30 Javascript
jQuery 动画与停止动画效果实例详解
2020/05/19 jQuery
vue组件中实现嵌套子组件案例
2020/08/31 Javascript
[00:19]CN DOTA NEVER DIE!VG夺冠rOtK接受采访
2019/12/23 DOTA
Python日志模块logging简介
2015/04/13 Python
python装饰器实例大详解
2017/10/25 Python
Python基于Floyd算法求解最短路径距离问题实例详解
2018/05/16 Python
Python容器使用的5个技巧和2个误区总结
2019/09/26 Python
解决pyinstaller打包运行程序时出现缺少plotly库问题
2020/06/02 Python
基于selenium及python实现下拉选项定位select
2020/07/22 Python
使用Python制作一个数据预处理小工具(多种操作一键完成)
2021/02/07 Python
EGO Shoes美国/加拿大:英国时髦鞋类品牌
2018/08/04 全球购物
澳大利亚床上用品、浴巾和家居用品购物网站:Bambury
2020/04/16 全球购物
毕业生求职信的经典写法
2014/01/31 职场文书
音乐器材管理制度
2014/01/31 职场文书
财政专业大学生职业生涯规划书
2014/09/17 职场文书
生日宴会祝酒词
2015/08/10 职场文书
JavaScript实现外溢动态爱心的效果的示例代码
2022/03/21 Javascript
Java存储没有重复元素的数组
2022/04/29 Java/Android