【decorator】装饰器



2019年10月27日    Author:Guofei

文章归类: 0xd0_设计模式    文章编号: 1004

版权声明:本文作者是郭飞。转载随意,标明原文链接即可。本人邮箱
原文链接:https://www.guofei.site/2019/10/27/decorator.html


实现

普通的装饰器

定义一个装饰器

def logger(func):
    def wrapper(*args, **kwargs):
        print('我准备开始执行:{} 函数了:'.format(func.__name__))

        # 真正执行的是这行。
        res = func(*args, **kwargs)

        print('我执行完啦。')
        return res

    return wrapper

使用装饰器

@logger
def add(x, y):
    print('{} + {} = {}'.format(x, y, x + y))
    return x + y

add(1, 2)

装饰器是一种让代码简洁的方案,上面这个代码实际上等价于下面这个

def add(x, y):
    print('{} + {} = {}'.format(x, y, x + y))


logger(add)(1, 2)

改进:

import functools


def logger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('我准备开始执行:{} 函数了:'.format(func.__name__))

        # 真正执行的是这行。
        func(*args, **kwargs)

        print('我执行完啦。')

    return wrapper


@logger
def add(x, y):
    print('{} + {} = {}'.format(x, y, x + y))


add(1, 2)
  • @functools.wraps(func)。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。
  • 如果删掉,会发现 add.__name__ 的名字变成了 wrapper

带参数的装饰器

def say_hello(contry):
    def deco(func):
        def wrapper(*args, **kwargs):
            if contry == "china":
                print("你好!")
            elif contry == "america":
                print('hello.')
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)

        return wrapper

    return deco


# 小明,中国人
@say_hello("china")
def xiaoming():
    pass


# jack,美国人
@say_hello("america")
def jack():
    pass

functools.wraps 同上

import functools


def say_hello(contry):
    def deco(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if contry == "china":
                print("你好!")
            elif contry == "america":
                print('hello.')
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)

        return wrapper

    return deco


# 小明,中国人
@say_hello("china")
def xiaoming():
    pass


# jack,美国人
@say_hello("america")
def jack():
    pass

普通的类装饰器

class logger(object):
    def __init__(self, func):
        # 这个方法用来接收被装饰的函数
        self.func = func

    def __call__(self, *args, **kwargs):
        # 用来实现装饰逻辑
        print("[INFO]: the function {func}() is running..." \
              .format(func=self.func.__name__))
        return self.func(*args, **kwargs)


@logger
def say(something):
    print("say {}!".format(something))


say("hello")

带参数的类装饰器

class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):  # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..." \
                  .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)

        return wrapper


@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))


say("hello")

更多内容:http://magic.iswbm.com/zh/latest/c03/c03_10.html

应用

1

我们要做一个 fibonacci 计算函数,自然想到用递归

def fibonacci(n):
    if n < 0:
        raise ValueError('n>=0')
    return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)

你会发现这里有大量重复计算,导致计算效率极低。自然想到改进,把已经计算好的函数值记录下来

known = {0: 0, 1: 1}
def fibonacci2(n):
    if n in known:
        return known[0]
    else:
        res = fibonacci2(n - 1) + fibonacci2(n - 2)
        known[n] = res
    return res

实验发现,这个速度快了很多。新问题,如果你想写一个包,里面有不同的递归算法(帕斯卡三角等),这样写会让代码一团乱麻,因为每个方法你都要定义一个known。

2

能显示运行时间的装饰器

import time


def timer(func):
    def d_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
        return result

    return d_func


@timer
def myfunc(a):
    time.sleep(2)
    print(a)


myfunc(3)

附:scipy的做法

来源:

from scipy import optimize
optimize.fmin

解释一下,ncalls是一个计数器,记录函数被调用的次数

def wrap_function(function, args):
    ncalls = [0]
    if function is None:
        return ncalls, None

    def function_wrapper(*wrapper_args):
        ncalls[0] += 1
        return function(*(wrapper_args + args))

    return ncalls, function_wrapper

参考资料

【印】Chetan Giridhar:《Python 设计模式》, 中国工信出版集团


您的支持将鼓励我继续创作!