python学习交流

Python 关键知识5:修饰器

2018-12-06  本文已影响22人  水之心

来源:AI 领域中的培养与开发 ——by 丁宁

1 Why?为什么会出现装饰器这个东西?

有时候,写一个闭包,仅仅是为了增强一个函数的功能。功能增强完了之后,只对增强了功能的最终函数感兴趣,装饰之前的函数引用就变得多余。因而出现了 func = decorated_by(func) 这种即席覆盖的写法。

一个例子:修改一个函数的 help 命令的帮助文档

def decorate(func):
    func.__doc__ += '\nDecorated by decorate.'
    return func


def add(x, y):
    '''Return the sum of x and y.'''
    return x + y


decorated_add = decorate(add)

分析:

改进版一:

def decorate(func):
    func.__doc__ += '\nDecorated by decorate.'
    return func

def add(x, y):
    '''Return the sum of x and y.'''
    return x + y

add = decorate(add)

分析:

改进版二: @ 语法糖的横空出世

def decorate(func):
    func.__doc__ += '\nDecorated by decorate.'
    return func


@decorate
def add(x, y):
    '''Return the sum of x and y.'''
    return x + y

分析:

分层封装,充分复用

改进版三:装饰器被单独封装在一个模块中:

  1. DecorateToolBox.py
class DecorateToolBox:
    @classmethod
    def decorate(self, func):
        func.__doc__ += '\nDecorated by decorate.'
        return func
  1. test.py
from DecorateToolBox import DecorateToolBox


@DecorateToolBox.decorate
def add(x, y):
    '''Return the sum of x and y.'''
    return x + y

分析:

总结

2 What?什么是装饰器?

2.1 装饰器的堆叠

def deco_1(func):
    print('running deco_1 ...')
    return func
    
def deco_2(func):
    print('running deco_2 ...')
    return func

@deco_1
@deco_2
def f():
    print('running f ...')
    
if __name__ == '__main__':
    f()

show:

running deco_2 ...
running deco_1 ...
running f ...

2 装饰器在导入时立即执行

def deco_1(func):
    print('running deco_1 ...')
    return func
    
def deco_2(func):
    print('running deco_2 ...')
    return func

@deco_1
@deco_2
def f():
    print('running f ...')
    
if __name__ == '__main__':
    pass

show:

running deco_2 ...
running deco_1 ...

2.2 带参数的装饰器

既然装饰器只能接受一个位置参数,并且是被动的接受解释器传过来的函数引用,那么如何实现带 数的装饰器呢?

问题分析:

  1. 限制条件一:装饰器本身只能接受一个位置参数
  2. 限制条件二:这个位置参数已经被被装饰函数的引用占据了
  3. 问题目标:希望装饰器能够使用外部传入的其他参数
  4. 推论:装饰器需要访问或修改外部参数

三种备选方案:

  1. 在装饰器内访问全局不可变对象,若需要修改,则使用 global 声明(不安全)
  2. 在装饰器内访问外部可变对象(不安全)
  3. 让装饰器成为闭包的返回(较安全)

方案:编写一个闭包,接受外部参数,返回一个装饰器

先来看一下参数化之前的装饰器:

registry = set()


def register(func):
    registry.add(func)
    return func


@register
def f1():
    print("running f1.")


@register
def f2():
    print("running f2.")


def f3():
    print("running f3.")


def main():
    f1()
    f2()
    f3()


if __name__ == '__main__':
    print(registry)
    main()

show:

{<function f2 at 0x000001F4F2E61400>, <function f1 at 0x000001F4F2E61730>}
running f1.
running f2.
running f3.

参数化之后的装饰器,增加了开关功能:

registry = set()


def register(flag=True):
    def decorate(func):
        if flag:
            registry.add(func)
        else:
            registry.discard(func)
        return func

    return decorate

# register(False) 被调用并返回一个装饰器
@register()
def f1():
    print("running f1.")


@register(False)  
def f2():
    print("running f2.")


@register(True)
def f3():
    print("running f3.")


def main():
    f1()
    f2()
    f3()


if __name__ == '__main__':
    print(registry)
    main()

show:

{<function f1 at 0x000001F4F2E61488>, <function f3 at 0x000001F4F2E61730>}
running f1.
running f2.
running f3.

分析:

此时, register 变量被使用了两次,第一次是后面的调用:()(调用之后才变成一个装饰器),第二次是前面的装饰: @ (装饰器符合仅能用于装饰器)

注意: register 不是装饰器; register()register(False) 才是

3 How?装饰器怎么用

从模仿开始

装饰器的常见使用场景

注册机制或授权机制(往往跟应用开发相关)

参数的数据验证或清洗(往往跟数据清洗或异常处理相关)

我们可以强行对输入参数进行特殊限制:

def require_ints(func):
    def temp_func(*args):
        if not all([isinstance(arg, int) for arg in args]):
            raise TypeError("{} only accepts integers as argument s.".format(
                func.__name__))
        return func(*args)

    return temp_func


def add(x, y):
    return x + y


@require_ints
def require_ints_add(x, y):
    return x + y


if __name__ == '__main__':
    print(add(1.0, 2.0))
    print(require_ints_add(1.0, 2.0))

show:

3.0



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-5-7f14452d18fc> in <module>()
     20 if __name__ == '__main__':
     21     print(add(1.0, 2.0))
---> 22     print(require_ints_add(1.0, 2.0))


<ipython-input-5-7f14452d18fc> in temp_func(*args)
      3         if not all([isinstance(arg, int) for arg in args]):
      4             raise TypeError("{} only accepts integers as argument s.".format(
----> 5                 func.__name__))
      6         return func(*args)
      7 


TypeError: require_ints_add only accepts integers as argument s.

复用核心计算模块,仅改变输出方式

让原本返回 Python 原生数据结构的函数变成输出 JSON 结构

import json


def json_output(func):
    def temp_func(*args, **kw):
        result = func(*args, **kw)
        return json.dumps(result)

    return temp_func


def generate_a_dict(x):
    return {str(i): i**2 for i in range(x)}


@json_output
def generate_a_dict_json_output(x):
    return {str(i): i**2 for i in range(x)}


if __name__ == '__main__':
    a, b = generate_a_dict(5), generate_a_dict_json_output(5)
    print(a, type(a))
    print(b, type(b))

show:

{'0': 0, '1': 1, '2': 4, '3': 9, '4': 16} <class 'dict'>
{"0": 0, "1": 1, "2": 4, "3": 9, "4": 16} <class 'str'>
上一篇下一篇

猜你喜欢

热点阅读