内容来源主要为总结的《head first Python》笔记
Python函数修饰符又称装饰器,可以为现有函数增加代码而不必修改现有函数代码。
要编写一个修饰符,需要了解四个问题:
1. 如何创建一个函数
2. 如何把一个函数作为参数传递到另一个函数
3. 如何从函数返回一个函数
4. 如何处理任意数量和类型的函数参数
创建函数
Python创建函数通过关键字def指定函数名,并列出函数可能有的参数(函数的输入)。还引入return关键字可选,用来向调用该函数的代码传回一个值。
例如: def add_num(a, b):
"""funtion description here """
#single line comments here
return a + b
Python解释器不需要指定函数参数或返回值的类型。任何对象均可作为参数发送给函数,并且函数允许将任何对象作为返回值传回。Python只检查是否提供了参数和返回值,从来不会检查类型。
函数作为参数传递
Python中一切皆对象,函数也是对象,有一个对象ID。分析以下例子。
例:>>> msg='hello world'
>>> def hello():
print(msg)
>>> id(msg)
3212332312
>>> id(hello)
3212336659
>>> type(msg)
<class 'str'>
>>> type(hello)
<class 'function'>
>>> hello()
hello world
当把hello函数传递给id和type, 这里没有调用hello, 只是把这个函数名作参数传递到这两个函数。
调用传入的函数
函数对象作为参数传递到一个函数时,这个函数可以调用所传入的函数对象。给出一个小示例,函数apply有两个参数:一个函数对象和一个值。
def apply(func value):
return func(value)
测试该函数如下:
>>> apply(print, 42)
42
>>> apply(len, 'python')
6
>>> apply(tpye, apply)
<class 'function'>
函数可以嵌套在函数中
Python中,函数代码组中的代码可以是任意代码,包括定义另一个函数的代码,通常称为嵌套或内部函数。同样可以从外部函数返回嵌套函数(实际返回的是一个函数对象)。
def outer():
def inner():
print('This is inner')
print('This is outer, invoking inner.')
inner()
函数inner嵌套在outer函数中。出了在outer的代码组中调用inner, 不能在其他任何地方调用这个函数。innter在outer的局部作用域。
如果一个函数很复杂,包含多行代码,把一些函数代码抽取到一个嵌套函数中就会很有意义。而且代码会更易读。
更常见的一个用法是:外围函数使用return语句返回嵌套函数作为它的返回值。修饰符就是采用这种方法来创建。
从函数返回一个函数
>>> def outer():
def inner():
print('This is inner.')
print('This is outer, returning inner')
return inner #不加()只使用函数名,就会得到函数对象
>>> i=outer()
This is outer, returning inner
>>> type(i)
<class 'function'>
>>> i()
This is inner.
从例中可看出, 首先将调用outer的结果赋给名为i的变量,调用i,会执行innter函数的代码。
处理任意数量和类型的函数参数
Python使用*接收一个任意的参数表(0个或多个参数)。如下例子:
>>> def myfunc(* args):
for a in args:
print(a, end=' ')
if args:
print()
>>> myfunc() #不提供参数
>>> myfunc(10) # 一个参数
10
>>> myfunc(1, 'two', 3, 'four') #参数可以混合多种类型
1 two 3 four
Python还可以直接使用*,当向函数提供一个列表作为参数,这个列表尽管可能包含多个值,会被处理为一项,即一个列表参数。为了指示解释器展开这个列表,把每个列表项作为一个单独的参数,调用函数时,需要在列表名前面加上*作为前缀。
>>> values=[1, 2, 3,4, 5, 6,7]
>>> myfunc(values)
[1, 2, 3, 4, 5, 6, 7]
>>> myfunc(*values)
1 2 3 4 5 6 7
Python使用**接收任意多个关键字参数,即参数字典。
>>> def myfunc2(**kwargs):
for k, v in kwargs.items():
print(k, v, sep='->', end=' ')
if kwargs:
print()
>>> myfunc2(a=10, b=20)
a->10 b->20
>>> myfunc2()
也可直接使用**。
>>> values={'a':1, 'b':2, 'c':'python'}
>>> myfunc2(**values)
a->1 b->2 c->python
当然, * 和**可以同时使用。
>>> def myfunc3(*args, ** kwargs):
if args:
for a in args:
print(a, end=' ')
print()
if kwargs:
for k, v in kwargs.items():
print(k, v, sep='->', end=' ')
print()
>>> myfunc3()
>>> myfunc3(1,2,3)
1 2 3
>>> myfunc3(a=10, b=20)
a->10 b->20
>>> myfunc3(1,2,3,a=10, b=20)
1 2 3
a->10 b->20
创建函数修饰符
要创建一个函数修饰符需要知道:
1. 修饰符是一个函数
2. 修饰符取被修饰函数作为参数
3. 修饰符返回一个新函数
4. 修饰符维护被修饰函数的签名
修饰符需要确保它返回的函数与被修饰函数有相同的参数(个数和类型都相同)。函数参数的个数和类型称为其签名。
举例,web相关的很多函数的前提是需要确保页面已登陆。所以需要创建判断是否登陆的修饰符。
创建一个函数如下:
def check_logged_in(fun): #有一个参数,即被修饰函数的函数对象
def wrapper(): #定义嵌套函数,名为wrapper
if 'login_in' in session:
return fun() #调用被修饰的函数,注意此处使用了(),代表函数调用
return 'you are not logged in.'
return wrapper #返回嵌套函数的函数对象
前面第四条提到,必须要确保返回的函数与被修饰的函数有同样的参数,一个修饰符应用到现有函数时,对这个现有函数的所有调用都会替换为调用修饰符返回的函数。所以,如果现有函数接收多少个参数,包装函数也必须对应接收同样多个参数。只需使wrapper支持任意数量和类型的参数即可。
def check_logged_in(fun):
def wrapper(*args, **kwargs): #接收任意数量和类型的参数
if 'login_in' in session:
return fun(*args, **kwargs)
return 'you are not logged in.'
return wrapper
functools模块的wraps函数
最后一个问题是函数如何向解释器标识自己的身份。Python标准库提供了一个模块来处理这些。只需导入模块functools, 然后调用wraps函数即可。
wraps函数实现为一个修饰符,所以实际上并不是调用这个函数,而是在自己创建的修饰符中修饰wrapper函数。
from functools import wraps
def check_logged_in(fun):
@wraps(func) #一定要传入func作为参数
def wrapper(*args, **kwargs): #接收任意数量和类型的参数
if 'login_in' in session:
return fun(*args, **kwargs)
return 'you are not logged in.'
return wrapper