装饰器
装饰器(decorator)用于为函数方法或类动态增加功能,在不改动原对象的基础上额外增加一些功能。
说起装饰器,很多文章都会说到闭包和函数作用域,这里就不绕了,只说一下装饰器的使用。
大多数装饰器会在内部定义一个函数,然后将其返回。装饰器本质上是一个高阶函数,调用原函数作为参数,返回一个函数对象。
用法如下,比如定义一个求和函数,额外增加一个判断参数类型的功能,这里提供两种写法,下面一种使用了@符号,@符号是装饰器的语法糖,这只是一种语法上的优化,实际执行过程一样。
def add(a,b):
return a+b
def checkParams(fn):
def wrapper(a,b):
if isinstance(a, (int,float)) and isinstance(b, (int,float)):
return fn(a,b)
else:
print("unsupported variables")
return wrapper
add = checkParams(add)
print(add(3,5))
def checkParams(fn):
def wrapper(a,b):
if isinstance(a, (int,float)) and isinstance(b, (int,float)):
return fn(a,b)
else:
print("unsupported variables")
return wrapper
@checkParams
def add(a,b):
return a+b
print(add(3,5))
带参数的装饰器
在上面的装饰器中,装饰器唯一的参数就是执行业务的函数。装饰器的语法允许在调用时,提供其它参数。
import logging
def use_logging(level):
def decorator(func):
def wrapper(*args,**kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args,**kwargs)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name="foo"):
print("I am %s" % name)
foo()
# python3 deco.py
WARNING:root:foo is running
I am foo
带参数的装饰器实际上是对原装饰器的一个函数封装,并返回一个装饰器。
functools.wraps
使用装饰器极大地复用了代码,但是它有一个问题,就是原函数的元信息不见了。
拿最上面的求和函数来说,原函数的__name__
应该是add,加上装饰器之后,__name__
就变成了wrapper。不难发现,原函数已经被装饰器内部的函数替代。
functools.wraps 就是用来解决这个问题的,wraps本身也是一个装饰器。它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
一个完整的装饰器写法如下:
import functools
def checkParams(fn):
@functools.wraps(fn)
def wrapper(a,b):
if isinstance(a, (int,float)) and isinstance(b, (int,float)):
return fn(a,b)
return wrapper
@checkParams
def add(a,b):
return a+b
print(add(3,5))
print(add.__name__)
# python3 deco.py
8
add
装饰器的调用顺序
装饰器是可以叠加使用的,这就需要弄明白调用顺序了。
@a
@b
@c
def f():
pass
等价于:
def f():
pass
f = a(b(c(f)))
最后,提一下内置装饰器,除了上面用的@functools.wraps
,还有@property
,@classmethod
,@staticmethod
以及@functools.lru_cache
,@functools.singledispatch
。
参考:
https://docs.python.org/3/library/functools.html#functools.wraps
https://docs.python.org/3/library/functions.html#property