装饰器

装饰器(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

posted @ 2017-11-05 21:45  KeithTt  阅读(231)  评论(0编辑  收藏  举报