Python之装饰器

内容来源主要为总结的《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 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章