本文摘自朱雷老师所著《Python工匠》一书内容,作为笔记予以记录。
学习本章内容,我印象最深的是“虽然函数可以消除重复代码,但绝不能只把它看成一种复用代码的工具,函数最重要的价值其实是创建抽象,而提供复用价值甚至可以算是抽象所带来的一种副作用。”
要写出好的函数,秘诀就在于设计好的抽象,且不要写太复杂的函数(导致抽象不精确),每个函数只应该包含一层抽象。
一、本章学习的知识要点:
(1)函数参数与返回相关的基础知识
(2)代码可维护性技巧
(3)函数与状态
(4)语言机制对函数的影响
二、常用技巧
1、别将可变类型作为函数的参数默认值
在编写函数时,经常需要为参数设置默认值。这些默认值可以是任何类型,比如字符串、数值、列表,等等。而当它是可变类型时,怪事儿就发生了。看下面这个函数:
def append_value(value,items=[]):
"""向items列表中追加内容,并返回该列表"""
items.append(value)
return items
多次调用上面这个函数,就会发现函数的行为和预想的会不一样:
>>> append_value('apple')
['apple']
>>> append_value('1223')
['apple', '1223']
>>> append_value(9999)
['apple', '1223', 9999]
>>>
可以看到,第二次及后续多次调用后的结果看,传入函数的参数items的值不再是函数定义的空列表[ ],而是变成了上一次执行后的值。
之所以出现这个问题,是因为Python函数的参数默认值只会在函数定义阶段被创建一次,之后不论再调用多少次,函数内拿到的默认值都是同一个对象。
深入点,通过查看函数对象的保留属性_ _defaults_ _(列表类型),可以看到这个值的变化:
>>> append_value('apple') # 第一次调用
['apple']
>>> append_value.__defaults__[0] # 调用函数后,items的缺省值:['apple']
['apple']
>>> append_value(999)
['apple', 999]
>>> append_value.__defaults__[0] # 再次调用函数后,items的缺省值:['apple', 999]
['apple', 999]
>>> append_value('中国')
['apple', 999, '中国']
>>> append_value.__defaults__[0]
['apple', 999, '中国']
>>> append_value.__defaults__[0].clear() # 清空缺省值,items的缺省值:[]
>>> append_value.__defaults__[0]
[]
>>> append_value('huawei') # 清空缺省值,再次执行函数,缺省值['huawei']
['huawei']
>>>
为了规避这样问题,使用None来替代可变类型默认值是比较常见的做法:
def append_value(value,items=None):
"""向items列表中追加内容,并返回该列表"""
if items is None:
items =[]
items.append(value)
return items
如上修改后,假如调用函数没有提供items参数,函数每次都会创建一个新的空列表,不会在出现之前的问题。
2、定义仅限关键字参数
Python里的函数不光支持通过有序位置参数(positional argument)调用,还能指定参数名,通过关键字参数(keyword argument)的方式调用。比如下面这个用户查询函数:
def query_users(limit,offset,min_followers_count,include_profile):
"""查询用户
...
:param min_followers_count: 最小关注者数量
:param include_profile: 结果包含用户详细档案
"""
...
# 使用位置参数,参数太多,时间长了,就容易忘记参数的意义
>>>query_users(20,0,100,True)
# 使用关键字参数,可以不严格按照函数定义参数的位置来传递
>>>query_users(limit=20,offset=0,min_followers_count=100,include_profile=True)
虽然关键字参数调用模式很有用,但是系统并没有强制要求。不过,下面这种语法将强制使用,否则会抛出异常错误:
# 注意参数列表中的 * 符号
def query_users(limit,offset,*,min_followers_count,include_profile):
"""查询用户
...
:param min_followers_count: 最小关注者数量
:param include_profile: 结果包含用户详细档案
"""
...
通过在参数列表中插入 * 符号,该符号后的所有参数都变成了仅限关键字参数,如果调用方仍然使用位置参数值,系统会抛出异常错误,正确用法:
query_users(20,0,min_followers_count=100,include_profile=True)
假如函数的参数较多(超过3个),使用关键字参数模式将大大提升代码的可读性。