Python装饰器

何为装饰器

装饰器是可调用的对象,其参数为一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后把它返回,或将其替换成另一个函数或者对象,示例如下:

@decorate
def target():
    print("run target")
target()
def target():
    print("run target")
target = decorate(target)
target()

上述两种写法得到的结果一样,最终执行的并不一定是target函数,而是decorate(target)返回的函数
比如我们把decorate定义为如下函数

def decorate(fun):
    print("run in decorate")
    def wrapper():
        print('run in wrapper')
        fun()
        print('finish run fun')
    return wrapper

@decorate
def target():
    print("run target")

print('start run target')
target()

可以看到执行结果如下:

run in decorate
start run target
run in wrapper
run target
finish run fun

从执行结果中可以看出结果问题

  1. 在定义target函数时就同时执行了decorate函数,即执行了target = decorate(target)
  2. 执行target()实际执行的是wrapper(),即decorate返回的函数

什么时候执行

从上述例子已经看出,装饰器在被装饰的函数定义的时候立即执行,一般是在模块导入的时候(即Python加载模块的时候)

如何传参数

可能会有人问被装饰的函数需要传递参数怎么处理,可以在定义wrapper函数的时候传入参数,示例如下:

def decorate(fun):
    def wrapper(name):
        fun(name)
    return wrapper

@decorate
def target(name):
    print("run target, name = %s" % name)

print('start run target')
target('test_name')
[输出结果]
start run target
run target, name = test_name

如果需要传多个参数,甚至参数个数不确定呢?可以使用*args、**kwargs,跟函数使用基本一致

def decorate(fun):
    def wrapper(**kargs):
        fun(**kargs)
    return wrapper

@decorate
def target(name = '', age = 1, num = 1):
    print("name = %s" % name)
    print("age = %d" % age)
    print("num = %d" % num)

print('start run target')
target(name = 'test_name', age = 20, num = 10)
[输出结果]
start run target
name = test_name
age = 20
num = 10
def decorate(fun):
    def wrapper(*args, **kargs):
        fun(*args, **kargs)
    return wrapper

@decorate
def target(name, age = 1, num = 1):
    print("name = %s" % name)
    print("age = %d" % age)
    print("num = %d" % num)

print('start run target')
target('test_name', age = 20, num = 10)

[输出结果]
start run target
name = test_name
age = 20
num = 10

带参数的装饰器

对于装饰器也需要参数,示例如下:

def using_level(level):
    print('level = %s' % level)
    def decorate(fun):
        print('run in decorate')
        def wrapper(*args, **kargs):
            fun(*args, **kargs)
        return wrapper
    return decorate

@using_level("level2")
def target(name, age = 1, num = 1):
    print("name = %s" % name)
    print("age = %d" % age)
    print("num = %d" % num)

print('start run target')
target('test_name', age = 20, num = 10)

[输出结果]
level = level2
run in decorate
start run target
name = test_name
age = 20
num = 10

从输出结果可以看出,在被装饰函数定义的时候,等同于调用
target = using_level("level2")(target),装饰器会自动发现传入的函数,并将被装饰函数传入到wrapper

类装饰器

装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,先看例子:

# 装饰器
def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'with_logging'
        print func.__doc__       # 输出 None
        return func(*args, **kwargs)
    return with_logging

# 函数
@logged
def f(x):
   """does some math"""
   return x + x * x

logged(f)

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

装饰器的执行顺序

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于
f = a(b(c(f)))

【参考】https://foofish.net/python-decorator.html

你可能感兴趣的:(Python,&,爬虫)