概念
函数装饰器也是一种函数,其参数是被装饰的函数。只在函数声明/定义的时候使用,而在函数调用时不显示体现
装饰器放在被装饰的函数之上,以@
开头,后面跟装饰器名字,以及可选参数,如下
@decoratorName[(args)]
def funcName(func_args):
func_suit
在调用函数时,如下
funcName(...)
会自动转换成,如下形式执行
decoratorName(funcName)
使用场景
python的装饰器类似于Spring框架中使用的AOP(面向切面编程)。
主要使用场景为:
- 受权框架
- 日志框架
- 类中的staticmethod/classmethod方法等
自定义装饰器
回顾
在学习python的函数时知道,python语言的函数类似go语言为一等公民,即可以当成一个普通的类型来使用。
- 函数可以赋值给另外一个变量
>>> def hi(name='alex'): ... print('hi %s' % name) ... >>> h = hi >>> h() hi alex >>>
- 函数可以做为另一个函数的参数
>>> def log(func, arg): ... func(arg) ... >>> log(hi, 'lucy') hi lucy >>>
- 函数可以定义在另一个函数体内,即嵌套定义
>>> def foo(): ... def bar(): ... print('call foo.bar()') ... bar() ... >>> foo() call foo.bar() >>>
- 函数可以做为另一个函数的返回值
>>> def foo(): ... def bar(): ... print('call foo.bar()') ... return bar ... >>> b = foo() >>> b() call foo.bar() >>>
装饰器函数即是通过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-------
>>>
这样我们就可以使用类的继承、覆盖等特性实现更复杂的场景
网友评论