第4章 函数与函数编程
4.1.1 函数的定义
函数的作用
是实现结构化编程的重要工具之一
把需要重复使用的具有相对独立的功能部分定义为函数
定义要点
函数的核心组成部分包括函数名、函数的参数列表、函数的返回值
函数的参数列表也称为函数的签名
函数的文档字符串也是函数的组成部分(非必须),属性名为__doc__
使用def关键字来定义函数
函数使用return语句返回运行结果,如果没有return语句函数会返回None
def 函数名 ( 参数列表 ):
"""文档字符串"""
函数体
def add ( x , y ):
"""
数值求和.
Args:
x: 第一个参数.
y: 第二个参数.
Returns: 两个参数之和.
"""
s = x + y
return s
数值求和.
Args:
x: 第一个参数.
y: 第二个参数.
Returns: 两个参数之和.
def fun ():
def inner_fun ():
print ( 'This is inner fun!' )
print ( 'This is fun!' )
inner_fun ()
4.1.2 函数的调用
参数传递
通过函数的名字来调用函数
调用其他函数的函数,称为主调函数
如果函数中定义了参数,在主调函数中还应当传入对应的参数
定义在参数列表中的参数,称为函数的__形参__(formal parameter)
由主调函数传入的参数,称为__实参__(actual parameter)
形参变量和实参变量的命名可以不同,在函数调用的过程中,实参会将其值传递给形参
n1 = 2
n2 = 3
add ( n1 , n2 )
形参与实参是相互独立的,改变形参的值并不会导致实参的值被改变
def fun ( formal_p ):
formal_p = [ 8 , 5 , 7 ]
actual_p = [ 1 , 4 , 2 ]
fun ( actual_p )
print ( actual_p )
如果参数传递的是可变数据类型,形参传递给实参的实际上是可变数据的引用
形参引用的改变不会导致实参引用发生变化,但是由于它们指向相同的数据,因此形参指向的数据被改变后实参所指向的数据当然也会被改变
def fun ( formal_p ):
formal_p [ 0 ] = 0
actual_p = [ 1 , 4 , 2 ]
fun ( actual_p )
print ( actual_p )
def copy ( seq ):
return [ copy ( o ) if type ( o ) is list else o for o in seq ]
lst = [ 1 , 2 , [ 3 , 4 , [ 5 , 6 , [ 7 , 8 , [ 9 , 10 , 11 ]]]]]
lst_new = copy ( lst )
lst_new [ 2 ][ 2 ][ 2 ][ 2 ][ 2 ] = 0
print ( lst )
print ( lst_new )
[1, 2, [3, 4, [5, 6, [7, 8, [9, 10, 11]]]]]
[1, 2, [3, 4, [5, 6, [7, 8, [9, 10, 0]]]]]
4.1.3 变量的作用域
x = 1
def fun ():
global x
x = 2
利用global将局部变量声明为全局变量
在函数内部能够访问全局变量,但是不能修改全局变量的取值
如果尝试在函数中修改全局变量的值,则相当于重新定义了一个同名的局部变量
使用global可以将局部变量声明为同名的全局变量,就可以对其进行修改了
x = 0
def fun ():
x = 1
print ( x )
fun ()
print ( x )
x = 0
def fun ():
global x
x = 1
print ( x )
fun ()
print ( x )
4.2 函数的参数
4.2.1 位置参数与关键字参数
位置参数:实参列表中的参数将数值传递给形参列表中相同位置的参数。这种方式也是我们最常接触到的参数传递方式
关键字参数:也称为命名参数,在参数传递时,实参利用形参变量的名将值传递给指定形参
def say ( greeting , describe , target ):
print ( f ' { greeting } { describe } { target } !' )
say ( 'Hello' , 'beautiful' , 'Python' ) # 位置参数
say ( target = 'Python' , greeting = 'Hello' , describe = 'beautiful' ) # 关键字参数
Hello beautiful Python!
Hello beautiful Python!
调用函数时也可以混合使用位置参数和关键字参数,但是要求所有位置参数必须出现在关键字参数之前
say ( 'Hello' , target = 'Python' , describe = 'beautiful' )
say ( greeting = 'Hello' , 'beautiful' , 'Python' )
File "<ipython-input-18-33a91a139779>", line 1
say(greeting='Hello', 'beautiful', 'Python')
^
SyntaxError: positional argument follows keyword argument
def say ( greeting , describe , * , target ):
print ( f ' { greeting } { describe } { target } !' )
say ( 'Hello' , 'beautiful' , target = 'Python' )
say ( 'Hello' , 'beautiful' , 'Python' )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 Hello beautiful Python!
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-19-7a9dfada02dc> in <module>
3
4 say('Hello', 'beautiful', target='Python')
----> 5 say('Hello', 'beautiful', 'Python')
TypeError: say() takes 2 positional arguments but 3 were given
def say ( greeting , / , describe , * , target ):
print ( f ' { greeting } { describe } { target } !' )
4.2.2 可选参数
定义函数的时候可以指定参数的默认值,有默认值的参数称为可选参数
在调用的时候
如果实参列表中没有给出可选参数的值,则函数体中会使用默认值进行计算
如果实参列表中给出可选参数的值,则其默认值会被覆盖掉
def say ( greeting , describe = 'beautiful' , target = 'Python' ):
print ( f ' { greeting } { describe } { target } !' )
say ( 'Hello' )
say ( 'Hello' , 'powerful' )
Hello beautiful Python!
Hello powerful Python!
def say ( greeting = 'Hello' , describe , target ):
print ( f ' { greeting } { describe } { target } !' )
File "<ipython-input-23-3c50971decba>", line 1
def say(greeting='Hello', describe, target):
^
SyntaxError: non-default argument follows default argument
4.2.3 可变参数
可变参数是指在函数调用时,实参的数量是不确定的,可以根据需要任意变化
两种可变参数
*修饰的参数用于收集实参中的位置参数
**修饰的参数用于收集实参中的关键字参数
def say ( greeting = 'Hello' , * describe ):
print ( type ( describe ))
print ( f ' { greeting } { " and " . join ( describe ) } Python!' )
say ( 'Hello' , 'beautiful' , 'powerful' , 'easy' )
<class 'tuple'>
Hello beautiful and powerful and easy Python!
*修饰的参数可位于形参列表的任意位置,但是它右侧的参数在调用时必须使用关键字参数
def say ( greeting , * describe , target ):
print ( f ' { greeting } { " and " . join ( describe ) } { target } !' )
say ( 'Hello' , 'beautiful' , 'powerful' , 'easy' , target = 'Python' )
Hello beautiful and powerful and easy Python!
**修饰的参数必须位置于形参列表的最后
可同时使用*和**来收集可变数量的位置参数和关键字参数
def say ( greeting , * describe , ** detail ):
print ( type ( describe ), type ( detail ))
print ( f ' { greeting } { " and " . join ( describe ) } Python!' )
details = [ f "It's { k } is { v } ." for k , v in detail . items ()]
print ( ' ' . join ( details ))
say ( 'Hello' , 'beautiful' , 'powerful' , 'easy' , syntax = 'elegant' , code = 'simple' )
<class 'tuple'> <class 'dict'>
Hello beautiful and powerful and easy Python!
It's syntax is elegant. It's code is simple.
4.2.4 参数分配
在函数调用时,使用这*或**构造实参列表
*用于将序列或元组拆解开来分配给形参中的位置参数
**用于将字典拆解开来分配给形参中的关键字参数
def say ( greeting , describe , target ):
print ( f ' { greeting } { describe } { target } !' )
para_tuple = ( 'Hello' , 'beautiful' , 'Python' )
say ( * para_tuple )
para_dict = { 'greeting' : 'Hello' , 'describe' : 'beautiful' , 'target' : 'Python' }
say ( ** para_dict )
Hello beautiful Python!
Hello beautiful Python!
4.3 函数的类型注解*
4.3.1 类型注解
def func(arg: type, optarg: type = default) -> return_type:
- arg:形参名;
- type:形参的类型;
- optarg:可选参数;
- default:默认值;
- return_type:返回值的类型。
def add ( x : int , y : int ) -> int :
return x + y
print ( add ( 1 , 1 ))
print ( add ( 1.0 , 1.0 ))
添加了类型注解的函数,可以使用__annotations__属性来查看注解信息
print ( add . __annotations__ )
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
mypy
Python解释器不会利用函数的类型注解来检查参数的类型,但是它们可以被一些第三方编译工具利用
mypy就是一个静态类型版本的Python实现
提供了一个基于CPython的编译器mypyc,它能够像静态语言那样先将Python代码编译为二进制文件,从而提高程序的运行效率
pip install mypy
将下面的代码保存为名为add.py的文件:
def add ( x : int , y : int ) -> int :
return x + y
add ( 1.0 , 1.0 )
利用mypy来执行,它显示出函数add的两个实参数值与形参的类型不匹配的错误。
$ mypy add.py
add.py:3: error: Argument 1 to "add" has incompatible type "float"; expected "int"
add.py:3: error: Argument 2 to "add" has incompatible type "float"; expected "int"
Found 2 errors in 1 file (checked 1 source file)
4.3.2 typing模块
对于一些复杂的数据类型,如list、tuple、set、dict等,类型注解并不能完全反映变量的结构
例:
下面的函数中,参数nums注解为一个list,但是nums中究为什么类型的数值并不确定,需要添加更为细节的信息
def average ( nums : list ) -> float :
n = len ( nums )
return sum ( nums ) / n
from typing import List
def average ( nums : List [ float ]) -> float :
n = len ( nums )
return sum ( nums ) / n
average ([ 1.0 , 4.0 , 2.0 , 8.0 , 5.0 , 7.0 ])
typing.List也可以嵌套使用,例如data: List[List[int]]=[[1, 4], [2, 8], [5, 7]]
typing.Tuple
由于tuple是不可变类型,因此可以为其每个元素添加类型注解
from typing import Tuple
def create_student ( name : str , age : int , gender : str ) -> Tuple [ str , int , str ]:
return ( name , age , gender )
create_student ( '张三' , 18 , '男' )
typing.Sequence
有时候函数的参数需要一个序列类型,list、tuple、str都可以,则可以使用tying.Sequence
from typing import Sequence
def average ( nums : Sequence [ int ]) -> float :
n = len ( nums )
return sum ( nums ) / n
average ([ 1 , 4 , 2 , 8 , 5 , 7 ])
average (( 1 , 4 , 2 , 8 , 5 , 7 ))
typing.Union
当变量类型可以是多种类型中的一种时,可以使用typing.Union
from typing import Sequence , Union
def average ( nums : Union [ Sequence [ int ], Sequence [ float ]]) -> float :
n = len ( nums )
return sum ( nums ) / n
average ([ 1.0 , 4.0 , 2.0 , 8.0 , 5.0 , 7.0 ])
average (( 1 , 4 , 2 , 8 , 5 , 7 ))
typing.Any
typing.Any表示变量可以是任意类型,下面的函数中参数seq被标注为元素类型任意的序列,返回值类型也是如此
from typing import Sequence , Any
def reverse ( seq : Sequence [ Any ]) -> Sequence [ Any ]:
return seq [ - 1 :: - 1 ]
typing.Optional
typing.Optional表示变量可以是指定的类型或者None
from typing import Union , Optional
def div ( dividend : Union [ int , float ], divisor : Union [ int , float ]) -> Optional [ float ]:
if divisor == 0 :
return None
else :
return dividend / divisor
from typing import NoReturn
def error ( info : str ) -> NoReturn :
raise Exception ( info )
类型
描述
typing.Dict
字典
typing.Set
集合
typing.Generator
生成器
typing.Iterator
迭代器
typing.Callable
可调用类型
4.3.3 类型注解的使用
类型注解在Python代码的运行过程中不起任何作用,使用与否不会对代码的功能产生影响。
什么情况下应该使用类型注解呢?
初学者暂时不必考虑使用类型注解;
在很短的代码模块中使用注解的意义不大;
若是为了开发供他人使用的工具包,最好使用类型注解;
较大的软件项目中要使用类型注解。
4.4 函数对象
4.4.1 一等对象
4.4.2 Python函数的面向对象特征
def fun1 ():
pass
def fun2 ():
pass
print ( id ( fun1 ), id ( fun2 ))
print ( id ( fun1 ) == id ( fun2 ))
4520867008 4520868304
False
def fun ():
"""This is doc string of the function"""
'This is doc string of the function'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 ['__repr__',
'__call__',
'__get__',
'__new__',
'__closure__',
'__doc__',
'__globals__',
'__module__',
'__code__',
'__defaults__',
'__kwdefaults__',
'__annotations__',
'__dict__',
'__name__',
'__qualname__',
'__hash__',
'__str__',
'__getattribute__',
'__setattr__',
'__delattr__',
'__lt__',
'__le__',
'__eq__',
'__ne__',
'__gt__',
'__ge__',
'__init__',
'__reduce_ex__',
'__reduce__',
'__subclasshook__',
'__init_subclass__',
'__format__',
'__sizeof__',
'__dir__',
'__class__']
4.4.3 Python函数的一等对象特征
函数赋值
Python中可以将函数赋值给一个变量,而被赋值的变量能够像函数一样被调用
也作为一个数据结构的元素
def say ( greeting , describe , target ):
print ( f ' { greeting } { describe } { target } !' )
new_say = say
new_say ( 'Hello' , 'beautiful' , 'python' )
def fun1 ():
print ( 'fun1' )
def fun2 ():
print ( 'fun2' )
fun_list = [ fun1 , fun2 ]
for f in fun_list :
f ()
高阶函数
能够接收函数作为参数,或者返回值为函数的函数
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - y
def compute ( x , y , fun ):
return fun ( x , y )
def sort_key ( e ):
return e [ 1 ]
lst = [( 1 , 4 ), ( 2 , 8 ), ( 5 , 7 )]
sorted ( lst , key = sort_key )
lst = [( 1 , 4 ), ( 2 , 8 ), ( 5 , 7 )]
sorted ( lst , key = lambda e : e [ 1 ])
f_str = '''
def dynamic_fun():
print('Hello dynamic function')
'''
exec ( f_str )
使用types.FunctionType动态创建函数
import types
fun_str = '''
def dynamic_fun():
print('Hello dynamic Python!')
'''
code_obj = compile ( fun_str , filename = '' , mode = 'exec' ) # mode: 'exec'、'eval'或'single'
fun = types . FunctionType ( code_obj . co_consts [ 0 ], globals ())
4.5 嵌套函数与闭包
4.5.1 嵌套函数
嵌套函数是指一个函数(外部函数)中定义了另一个函数(内部函数)
是一种动态创建函数的方法
内部函数只能在外部函数中使用
有一定程度的“封装”作用
def outer ():
def inner ():
print ( "This is from inner" )
print ( "This is from outer" )
inner ()
This is from outer
This is from inner
nonlocal
外部函数中定义的变量相当于其内部的全局变量,在内部函数中能够访问但不能修改
使用nonlocal在内部函数中将变量称为非局部变量,就能够对其进行修改了
def outer ():
x = 0
def inner ():
nonlocal x
x = 1
inner ()
print ( x )
nonlocal只能声明外部函数中已定义的变量为非局部变量。若内部函数中声明了外部未定义的变量,则会抛出错误
def outer ():
def inner ():
nonlocal x
x = 0
inner ()
File "<ipython-input-6-a818351f88c3>", line 3
nonlocal x
^
SyntaxError: no binding for nonlocal 'x' found
4.5.2 闭包
概念与原理
def create_averager ():
numbers = []
def avg ( nums ):
if isinstance ( nums , list ):
numbers . extend ( nums )
else :
numbers . append ( nums )
return sum ( numbers ) / len ( numbers )
return avg
averager = create_averager ()
闭包的运行原理
普通函数调用的特点
会在栈上压入函数的参数及函数中定义的局部变量,构建执行环境
函数执行完毕返回时,运行环境会被销毁,参数与局部变量也将不复存在
再次调用函数会生成新的环境
闭包的不同之处
外部函数在返回的时候,局部变量作为自由变量被内部函数所引用
内部函数被作为闭包返回至外部环境
闭包被销毁之前,它对外部函数中定义的自由变量的引用始终存在,因而自由变量不会被销毁
自由变量成为一种特殊的存在,它即不是全局变量,也不是局部变量
闭包的根本作用
闭包的使用要点
闭包并非由嵌套函数定义,而是通过调用外层函数创建出来
每次调用都会创建出新的闭包
不同的闭包之间是相互独立的,它们各自有着自己的自由变量
averager1 = create_averager ()
averager2 = create_averager ()
利用闭包的__code__属性,可以查看局部变量和自由变量
averager . __code__ . co_freevars
averager . __code__ . co_varnames
自由变量的值,保存在__closure__属性之中,它是一个元素为cell对象的元组
averager . __closure__ [ 0 ] . cell_contents
当嵌套函数的外部函数中定义了多个内部函数并返回它们,并且它们引用了相同的自由变量,那么返回的函数和自由变量处于同一个闭包之中,共享相同的自由变量
def create_averagers ():
numbers = []
def avg1 ( num ):
numbers . append ( num )
return sum ( numbers ) / len ( numbers )
def avg2 ( num ):
numbers . append ( num )
return sum ( numbers ) / len ( numbers )
return avg1 , avg2
averager1 , averager2 = create_averagers ()
实例
利用闭包实现任意一元函数
一元\(n\) 次函数可写为:
$$
f(x) = p_0x^n + p_1x^{n-1} + p_2 x^{n-2} + \cdots + p_{n-1}x + p_{n}
$$
给定一组参数\(p=(p_0,p_1,p_2,\cdots,p_n)\) 就确定了一个一元函数。下面的例子中,外层函数接收定义一元函数的参数\(p\) ,返回一个由该参数确定的一元函数。
def create_univariate_func ( * prams ):
def univariate_func ( x ):
y = 0
order = len ( prams ) - 1
for i , p in enumerate ( prams ):
y += p * x ** ( order - i )
return y
return univariate_func
当参数为\(p=(1,2,3)\) 时,定义的一元函数为\(f(x) = x^2+2x+3\) 。
uni_func = create_univariate_func ( 1 , 2 , 3 )
4.6 函数装饰器
装饰器(Decorator)是用于修饰函数、类或者方法的一种特殊的工具,它能够在不 侵入代码的情况下增加或者改变修饰目标的功能
利用函数实现
利用类实现
4.6.1 简单函数装饰器
import time
def run_time ( fun ):
def wrapper ():
start = time . time ()
result = fun ()
end = time . time ()
print ( f '函数 { fun . __name__ } 的运行时间为 { ( end - start ) : .4 } 秒' )
return result
return wrapper
装饰器的名字就是定义的嵌套函数中外层函数的名字
由于装饰器没有侵入定义函数的代码,因此在被修饰函数内部感知不到装饰器的存在
@run_time
def fun1 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
@run_time
def fun2 ():
for _ in range ( 10 ):
time . sleep ( 0.2 )
装饰器同样不会侵入调用函数的代码,因此经过装饰器修饰的函数在调用时与普通函数没有任何区别
4.6.2 函数装饰器的工作原理
装饰器的实际执行过程
函数装饰器只是一种令代码更加简洁的语法糖
下面的例子实现了与装饰器完全相同的功能
def fun1 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
def fun2 ():
for _ in range ( 10 ):
time . sleep ( 0.2 )
两种方法是等价的,
装饰器的优势是完全不必改变任何函数定义或调用的代码逻辑,就能够为函数增加新的功能或行为
装饰器的运行时机
装饰器的执行过程包括如下几个步骤
定义装饰器
定义函数
装饰函数
调用装饰之后的函数
它们的运行顺序是怎样的呢?
将下面的代码保存为一个脚本文件run_chance.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 def run_chance ( fun ):
print ( f '装饰函数 { fun . __name__ } ' )
return fun
@run_chance
def fun1 ():
print ( '运行函数fun1' )
@run_chance
def fun2 ():
print ( '运行函数fun2' )
if __name__ == '__main__' :
fun1 ()
fun2 ()
在终端运行python run_chance.py,输出结果为:
$ python run_chance.py
装饰函数 fun1
装饰函数 fun2
运行函数fun1
运行函数fun2
装饰器对函数的修饰不是在函数调用时执行,而是在函数定义时就执行的
输出结果为:
结果表明,装饰器在导入模块时就被执行了
装饰器与闭包的关系
装饰器就是闭包
装饰器外层函数的参数为函数
返回为内层定义的函数
内层函数中引用了外层中的形参变量
4.6.3 函数装饰器的优化
含参函数的装饰
修饰函数包含参数,则需要内层函数参够处理这些参数
利用函数的可变参数方便地实现
import time
def run_time ( fun ):
def wrapper ( * args , ** kwargs ):
start = time . time ()
result = fun ( * args , ** kwargs )
end = time . time ()
print ( f '函数 { fun . __name__ } 的动行时间为 { ( end - start ) : .4 } 秒' )
return result
return wrapper
@run_time
def fun1 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
@run_time
def fun2 ( t ):
for _ in range ( t ):
time . sleep ( 0.1 )
被修饰函数的元信息
经过修饰的函数与被修饰函数并非同一个函数
函数的元信息丢失,例如__name__以及__doc__等
为了保留函数的元信息,需要在装饰器定义时手动地将被修饰函重要的元信息保存至内层函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import time
def run_time ( fun ):
def wrapper ( * args , ** kwargs ):
start = time . time ()
result = fun ( * args , ** kwargs )
end = time . time ()
print ( f '函数 { fun . __name__ } 的动行时间为 { ( end - start ) : .4 } 秒' )
return result
wrapper . __name__ = fun . __name__
wrapper . __doc__ = fun . __doc__
return wrapper
@run_time
def fun ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
使用functools.wraps装饰器实现元信息的复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import time
from functools import wraps
def run_time ( fun ):
@wraps ( fun )
def wrapper ( * args , ** kwargs ):
start = time . time ()
result = fun ( * args , ** kwargs )
end = time . time ()
print ( f '函数 { fun . __name__ } 的动行时间为 { ( end - start ) : .4 } 秒' )
return result
return wrapper
@run_time
def fun ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
print ( 'fun' )
利用functools.update_wrapper函数实现元信息的复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import time
from functools import update_wrapper
def run_time ( fun ):
def wrapper ( * args , ** kwargs ):
start = time . time ()
result = fun ( * args , ** kwargs )
end = time . time ()
print ( f '函数 { fun . __name__ } 的动行时间为 { ( end - start ) : .4 } 秒' )
return result
return update_wrapper ( wrapper , fun )
@run_time
def fun ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
print ( 'fun' )
__wrapped__属性
使用wraps装饰器或update_wrapper函数之后,可以通过被修饰函数的__wrapped__属性获得原始函数的引用
4.6.4 装饰器的叠加*
叠加装饰器的定义与使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 def dec1 ( fun ):
def wrapper ( * args , ** kwargs ):
print ( 'dec1' )
return fun ( * args , ** kwargs )
return wrapper
def dec2 ( fun ):
def wrapper ( * args , ** kwargs ):
print ( 'dec2' )
return fun ( * args , ** kwargs )
return wrapper
@dec1
@dec2
def fun ():
print ( 'fun' )
叠加装饰器的原理
函数叠加使用多个装饰器修饰时,其作用原理与单个装饰器完全相同
只不过,外层装饰器的修饰对象并不是函数,而是内层装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14 def dec1 ( fun ):
def wrapper ( * args , ** kwargs ):
print ( 'dec1' )
return fun ( * args , ** kwargs )
return wrapper
def dec2 ( fun ):
def wrapper ( * args , ** kwargs ):
print ( 'dec2' )
return fun ( * args , ** kwargs )
return wrapper
def fun ():
print ( 'fun' )
叠加装饰器的执行顺序
被修饰函数调用之前的代码先执行
执行顺序为先外层后内层
接下来执行被修饰函数
最后执行被修饰函数之后的代码
执行顺序为先内层后外层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 from functools import wraps
def dec1 ( fun ):
@wraps ( fun )
def wrapper ( * args , ** kwargs ):
print ( 'dec1 before ...' )
result = fun ( * args , ** kwargs )
print ( 'dec1 after ...' )
return result
return wrapper
def dec2 ( fun ):
@wraps ( fun )
def wrapper ( * args , ** kwargs ):
print ( 'dec2 before ...' )
result = fun ( * args , ** kwargs )
print ( 'dec2 after ...' )
return result
return wrapper
@dec1
@dec2
def fun ():
print ( '完成fun的功能' )
dec1 before ...
dec2 before ...
完成fun的功能
dec2 before ...
dec1 after ...
4.6.5 含参装饰器
装饰器实际上是一种高阶函数
函数可以有参数,因此装饰器也可以有参数
定义带参数的装饰器需要使用一个三层的嵌套函数
最外层的函数称为装饰器工厂函数
内部的两层函数是真正的装饰器定义
1
2
3
4
5
6
7
8
9
10
11
12
13 def param_dec ( arg1 , arg2 ):
def dec ( fun ):
def wrapper ( * args , ** kw ):
print ( f "before actions based on { arg1 } and { arg2 } ..." )
result = fun ( * args , ** kw )
print ( f "after actions based on { arg1 } and { arg2 } ..." )
return result
return wrapper
return dec
@param_dec ( 'arg1' , 'arg2' )
def fun ():
print ( '完成fun的功能' )
before actions based on arg1 and arg2 ...
完成fun的功能
after actions based on arg1 and arg2 ...
能够根据参数将函数运行时间的单位显示为秒、毫秒或者微秒的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import time
from datetime import datetime
from functools import wraps
def run_time ( unit ):
def dec ( fun ):
@wraps ( fun )
def wrapper ( * args , ** kwargs ):
start = datetime . now ()
result = fun ( * args , ** kwargs )
end = datetime . now ()
if unit == 'ms' :
time_str = f ' { ( end - start ) . total_seconds () * 1000 : .0f } 毫秒'
elif unit == 'us' :
time_str = f ' { ( end - start ) . total_seconds () * 10 ** 6 : .0f } 微秒'
else :
time_str = f ' { ( end - start ) . total_seconds () : .0f } 秒'
print ( f '函数 { fun . __name__ } 的动行时间为 { time_str } ' )
return result
return wrapper
return dec
1
2
3
4
5
6
7
8
9
10
11
12
13
14 @run_time ( 's' )
def fun1 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
@run_time ( 'ms' )
def fun2 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
@run_time ( 'us' )
def fun3 ():
for _ in range ( 10 ):
time . sleep ( 0.1 )
4.6.6 函数装饰器应用实例 *
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 from inspect import signature
from functools import wraps
def type_check ( fun ):
@wraps ( fun )
def wrapper ( * args , ** kwargs ):
sig = signature ( fun ) # 函数的签名
param_dict = sig . parameters # 函数的{参数: 类型}字典
param_list = list ( param_dict . values ())
for i , k in enumerate ( args ): # 检查位置参数
assert type ( k ) is param_list [ i ] . annotation , \
f '第 { i + 1 } 个参数的类型必须为 { param_list [ i ] . annotation } '
for k , v in kwargs . items (): # 检查关键字参数
assert type ( v ) is param_dict [ k ] . annotation , \
f '参数 { k } 的类型必须为 { param_dict [ k ] . annotation } '
return fun ( * args , ** kwargs )
return wrapper
@type_check
def fun ( x : int , y : float ):
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 ---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-44-3f43dc183e44> in <module>
----> 1 fun(1, 2)
<ipython-input-42-2b636f173ed3> in wrapper(*args, **kwargs)
9 param_list = list(param_dict.values())
10 for i, k in enumerate(args): # 检查位置参数
---> 11 assert type(k) is param_list[i].annotation,\
12 f'第{i+1}个参数的类型必须为{param_list[i].annotation}'
13 for k, v in kwargs.items(): # 检查关键字参数
AssertionError: 第2个参数的类型必须为<class 'float'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 ---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-45-be3945dfd467> in <module>
----> 1 fun(1.0, 2)
<ipython-input-42-2b636f173ed3> in wrapper(*args, **kwargs)
9 param_list = list(param_dict.values())
10 for i, k in enumerate(args): # 检查位置参数
---> 11 assert type(k) is param_list[i].annotation,\
12 f'第{i+1}个参数的类型必须为{param_list[i].annotation}'
13 for k, v in kwargs.items(): # 检查关键字参数
AssertionError: 第1个参数的类型必须为<class 'int'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 ---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-46-85690ed67ee6> in <module>
----> 1 fun(1, y=2)
<ipython-input-42-2b636f173ed3> in wrapper(*args, **kwargs)
12 f'第{i+1}个参数的类型必须为{param_list[i].annotation}'
13 for k, v in kwargs.items(): # 检查关键字参数
---> 14 assert type(v) is param_dict[k].annotation, \
15 f'参数{k}的类型必须为{param_dict[k].annotation}'
16 return fun(*args, **kwargs)
AssertionError: 参数y的类型必须为<class 'float'>
4.6.7 重要的 Python 内置装饰器 *
lru_cache
将被修饰函数的运行结果缓存起来,后续调用的时候如果函数参数与已缓存结果的参数相同,则不再执行函数,直接将缓存的结果返回
from functools import lru_cache
@lru_cache
def fun ( i ):
print ( f '计算 { i } 的平方' )
return i ** 2
for _ in range ( 5 ):
print ( fun ( 1 ))
lru_cache的参数
maxsize:缓存的大小
typed:缓存结果会考虑参数的数据类型
from functools import lru_cache
@lru_cache ( typed = True )
def fun ( i ):
print ( f '计算 { i } 的平方' )
return i ** 2
lru_cache的缓存是利用字典来实现的
字典的键基于被修饰函数的参数来构建的
求被修饰函数的参数必须是可哈希的(hashable)
from functools import lru_cache
@lru_cache ( 10 )
def average ( values ):
n = len ( values )
return sum ( values ) / n
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-55-5e145017fa0a> in <module>
----> 1 average([1, 2, 3])
TypeError: unhashable type: 'list'
singledispatch
使用singledispatch装饰器可实现类似于函数重载的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 from functools import singledispatch
@singledispatch
def average ( data ):
n = len ( data )
return sum ( data ) / n
@average . register ( dict ) # 参数为字典时
def _ ( data ):
data = data . values ()
n = len ( data )
return sum ( data ) / n
@average . register ( str ) # 参数为字符串时
def _ ( data ):
data = [ float ( i ) for i in data . split ( ',' )]
n = len ( data )
return sum ( data ) / n
average ({ 'a' : 1 , 'b' : 2 , 'c' : 3 })
singledispatch将被修饰函数转变成一个名为xxx.register的装饰器
与使用xxx.register修饰的一组函数被绑定在一起,组成了一个泛函数(Generic Function)
当调用函数的时候,会根据参数的类型确定被实际被调用的函数
被xxx.register修饰的函数名字是无关紧要的,因此常使用_来替代
4.7 常用函数编程工具*
4.7.1 常用工具函数
functools.partial
固定一个函数的部分参数值,返回一个以其余参数为参数的可调用对象
函数fun是以a、b、c为参数的一元二次函数\(ax^2+bx+c\)
def fun ( a , b , c , x ):
return a * x ** 2 + b * x + c
利用partial将参数a、b、c固定为1、2、3,返回的\(f\) 表示\(x^2+2x+3\) 。
from functools import partial
f = partial ( fun , 1 , 2 , 3 )
map与filter
map函数的作用是将一个函数作用于可迭代对象中的每一个元素之上,并返回由每个元素的运算结果构成的新的可迭代对象
filter的作用是对可迭代对象的元素进行过滤
lst = [ 1 , 4 , 2 , 8 , 5 , 7 ]
lst_square = map ( lambda x : x ** 2 , lst )
list ( lst_square )
[ 1 , 16 , 4 , 64 , 25 , 49 ]
lst_f = filter ( lambda x : x % 2 == 0 , lst )
list ( lst_f )
functools.reduce
函数fun必须接受两个参数,并返回一个值
它首先接受可迭代对象的前两个元素A和B,并计算fun(A, B),然后处理第三个元素C,计算fun(fun(A, B), C),以次类推直至将可迭代对象中的元素处理完毕
functools.reduce返回最后一次fun的执行结果
from functools import reduce
lst = [ 1 , 4 , 2 , 8 , 5 , 7 ]
reduce ( lambda r , e : r * e , lst )
any和all
检查一个可迭代对象的逻辑值,并返回True或False
any在可迭代对象中的任意元素的逻辑值为真时返回True
all则在所有元素的逻辑值都为真时返回True
lst = [ 1 , 4 , 2 , 8 , 5 , 7 , 0 ]
any ( lst )
4.7.2 operator模块
operator模块中包含了一系列对应于 Python 运算符的函数
数学运算: add,sub,mul,truediv,floordiv;
逻辑运算: not_,truth;
比较运算: eq,ne,lt,le,gt, ge。
对象确认: is_,is_not。
函数
功能描述
count(start,step)
创建一个从start开始,步长为step的可迭代对象
cycle(iter)
创建一个将可迭代对象iter重复无限多次的可迭代对象
repeat(elem, [n])
创建一个将元素elem重复n次的可迭代对象,n为空时重复无限多次
chain(iterA, iterB, ...)
将多个可迭代对象拼接起来生成一个新的可迭代对象
islice(iter, [start], stop, [step])
截取iter的部分元素构成新的可迭代对象
tee(iter, [n])
将可迭代对象iter复制n次,n默认值为2
starmap(func, iter)
iter为以元组为元素的可迭代对象,starmap以iter的元素作为函数func的参数,返回func的执行结果构成的可迭代对象
filterfalse(predicate, iter)
与filter函数相反,返回所有让 predicate 返回False 的元素
takewhile(predicate, iter)
返回一直让 predicate返回 True 的元素,一旦 predicate 返回 False就终止迭代
dropwhile(predicate, iter)
与takewhile相反,当predicate返回True 的时候丢弃元素,返回可迭代对象的其余元素
compress(data, selectors)
返回data中selectors为True的相应位置的元素
groupby(iter,key_func=None)
以key_func对iter中元素的处理结果为key,对iter中的元素进行分组
本页面的全部内容在 生信资料 bio.0594codes.cn 和 莆田青少年编程俱乐部 0594codes.cn 协议之条款下提供,附加条款亦可能应用