python学习笔记之--函数装饰器

2022-01-16  本文已影响0人  itsenlin

概念

函数装饰器也是一种函数,其参数是被装饰的函数。只在函数声明/定义的时候使用,而在函数调用时不显示体现
装饰器放在被装饰的函数之上,以@开头,后面跟装饰器名字,以及可选参数,如下

@decoratorName[(args)]
def funcName(func_args):
    func_suit

在调用函数时,如下

funcName(...)

会自动转换成,如下形式执行

decoratorName(funcName)

使用场景

python的装饰器类似于Spring框架中使用的AOP(面向切面编程)。
主要使用场景为:

  1. 受权框架
  2. 日志框架
  3. 类中的staticmethod/classmethod方法等

自定义装饰器

回顾

在学习python的函数时知道,python语言的函数类似go语言为一等公民,即可以当成一个普通的类型来使用。

装饰器函数即是通过python函数的这些特性来实现的,具体实现参见下面章节

不带参数的装饰器

既然装饰器是为了装饰函数、给原有函数增加特殊功能,说明最主要的还是要调用到被装饰函数,这样我们可以把被装饰函数做为参数传给装饰器,在装饰器中实现一个嵌套函数,此内嵌函数实现对被装饰函数的封装及调用,如下

>>> def decorator_test(func):
...     def wrapper_func():
...         print("--------before-------")
...         func()
...         print("--------after-------")
...     return wrapper_func
... 
>>> @decorator_test
... def foo():
...     print('call foo()')
... 
>>> foo()
--------before-------
call foo()
--------after-------
>>>

但是这样会有一个问题,foo函数虽然调用时跟不使用装饰器一致,但是实际上基__name__变量已经被修改为装饰器里面的内嵌函数了,如下

>>> foo.__name__
'wrapper_func'
>>>

有没有办法即使用装饰器,又不要改变原有函数的一些属性呢(有可能这些属性会被使用)?

python提供了一个functools模块,里面的wraps函数(也是一个装饰器)可以实现复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。使用方法如下

>>> from functools import wraps
>>> 
>>> def decorator_test(func):
...     @wraps(func)
...     def wrapper_func():
...         print("--------before-------")
...         func()
...         print("--------after-------")
...     return wrapper_func
... 
>>> @decorator_test
... def foo():
...     print('call foo()')
... 
>>> foo()
--------before-------
call foo()
--------after-------
>>> foo.__name__
'foo'
>>>

带参数的装饰器

如果装饰器函数需要使用参数,则需要在不带参数的装饰器场景下再增加一层函数嵌套,最外层返回一个装饰器,具体实现参见下面这个例子

>>> from functools import wraps
>>> 
>>> def log(txt='call'):
...     def decorator_test(func):
...         @wraps(func)
...         def wrapper_func():
...             print("--------before %s-------" % txt)
...             func()
...             print("--------after %s-------" % txt)
...         return wrapper_func
...     return decorator_test
... 
>>> @log('calling func')
... def foo():
...     print('call foo()')
... 
>>> foo()
--------before calling func-------
call foo()
--------after calling func-------
>>>

可以对比一下前面不带参数的装饰器的例子

被装饰的函数带参数情况

前面的例子中被装饰的函数foo()都是无参情况,实现比较简单。
但是一般情况下装饰器都是一些比较通用的,装饰器一般不知道被装饰的函数是否有参数,以及有几个参数,这种情况该如何处理呢?

这个就使用到python函数不定长参数的实现方式了,具体实现如下

>>> from functools import wraps
>>> 
>>> def log(txt='call'):
...     def decorator_test(func):
...         @wraps(func)
...         def wrapper_func(*args, **kwargs):
...             print("--------before %s-------" % txt)
...             func(*args, **kwargs)
...             print("--------after %s-------" % txt)
...         return wrapper_func
...     return decorator_test
... 
>>> @log('calling func')
... def foo():
...     print('call foo()')
... 
>>> @log()
... def bar(name='alex'):
...     print('hi %s' % name)
... 
>>> foo()
--------before calling func-------
call foo()
--------after calling func-------
>>> bar()
--------before call-------
hi alex
--------after call-------
>>> bar('lucy')
--------before call-------
hi lucy
--------after call-------
>>>

装饰器类

python中有一个特殊内部函数__call__可以实现把类对象当成函数形式的使用

例如:
a为一个类对象,b为一个类中定义的方法,访问需要通过a.b()形式;
如果类中实现了__call__,对象a可以被当成一个函数使用

如下:

>>> class Demo:
...     def __init__(self, a, b):
...         self.a = a
...         self.b = b
...     def my_print(self,):
...         print("a = ", self.a, "b = ", self.b)
...     def __call__(self, *args, **kwargs):
...         self.a = args[0]
...         self.b = args[1]
...         print("call: a = ", self.a, "b = ", self.b)
... 
>>> a = Demo(1, 2)
>>> a.my_print()
a =  1 b =  2
>>> a(3, 4)
call: a =  3 b =  4
>>> 

根据类的这个特性,我们可以在__call__中实现装饰器,这样类名就可以做为装饰器使用了

>>> from functools import wraps
>>> 
>>> class log:
...     def __init__(self, txt='call'):
...         self.txt = txt
...     def __call__(self, func):
...         @wraps(func)
...         def wrapper_func(*args, **kwargs):
...             print("--------before %s-------" % self.txt)
...             func(*args, **kwargs)
...             print("--------after %s-------" % self.txt)
...         return wrapper_func
... 
>>> @log('calling func')
... def foo():
...     print('call foo()')
... 
>>> @log()
... def bar(name='alex'):
...     print('hi %s' % name)
... 
>>> foo()
--------before calling func-------
call foo()
--------after calling func-------
>>> bar()
--------before call-------
hi alex
--------after call-------
>>> bar('lucy')
--------before call-------
hi lucy
--------after call-------
>>> 

这样我们就可以使用类的继承、覆盖等特性实现更复杂的场景

上一篇 下一篇

猜你喜欢

热点阅读