Python Decorator

Reason is the light and the light of life.

Jerry Su Dec 20, 2020 2 mins

@lock vs @lock() 装饰器 vs 装饰器工厂函数

相同

  • 均是导入时运行,返回值均是函数。

不同

  • 装饰器返回的是替代被装饰的函数,装饰器工厂函数返回的是装饰器函数

  • 装饰器传入的是函数(被装饰的函数),装饰器工厂函数传入的是参数(传给装饰器的参数)

0. 装饰器

装饰器:参数和返回值必须是函数,即接收一个函数(被装饰的函数)对象,返回一个函数(已装饰的函数)对象。动态的给一个对象添加额外职责,即装饰器设计模式。

装饰器的典型行为:把被装饰的函数替换为新函数,二者接收的参数相同,并返回被装饰函数本该的返回值,同时增加一些额外操作。

func = factorial, factorial = clocked

clock是装饰器

factorial是被装饰函数

clocked是被装饰后的函数

factorial作为func参数传递给装饰器clock,装饰器clock返回clocked函数并复制给factorial。func是clocked的自由变量,两者构成闭包。

当调用函数factorial时,实际上调用函数locked,而locked函数中func则为被装饰的函数factorial。

# 实现一个简单的装饰器:调用被装饰的函数时,打印被调用函数的运行时间,传入参数和输出结果。

import time

def clock(func):
    def clocked(*args):
        start = time.time()
        result = func(*args)
        end = time.time() - start
        arg_str = ''.join([repr(arg) for arg in args])
        print(f'time: {end}, args: {arg_str}, result: {result}')
        return result
    return clocked

@clock
def factorial(n):
    return 1 if n < 2 else n * factorial(n - 1)
factorial(6)
time: 4.76837158203125e-07, args: 1, result: 1
time: 0.0006880760192871094, args: 2, result: 2
time: 0.0008151531219482422, args: 3, result: 6
time: 0.0009067058563232422, args: 4, result: 24
time: 0.0010030269622802734, args: 5, result: 120
time: 0.0011680126190185547, args: 6, result: 720





720
factorial.__name__  # 可见factorial是函数locked的引用, 调用factorial(n)即调用locked(n)
'clocked'

1. 装饰器何时运行?

导入时与运行时:函数装饰器在导入模块时立即执行,而被装饰器装饰的函数,只有程序运行时显式调用时才执行。

装饰器在实际工程中:

  • 通常,装饰器在一个py模块中定义,应用到其他py模块的函数上。

  • 通常,装饰器内部绘重新定义一个函数,将其返回。而非作为装饰器参数的函数。

registry = []

def register(func):
    print(f'running register {func}')
    registry.append(func)
    return func

@register
def func1():
    print('running func1')

@register
def func2():
    print('running func2')

def func3():
    print('running fucn3')

def main():
    print(registry)
    func1()
    func2()
    func3()
running register <function func1 at 0x7ff5301fe8b0>
running register <function func2 at 0x7ff531b69790>
registry
[<function __main__.func1()>, <function __main__.func2()>]
main()
[<function func1 at 0x7ff5301fe8b0>, <function func2 at 0x7ff531b69790>]
running func1
running func2
running fucn3

2. 闭包

多数装饰器会修改被装饰的函数。通常是在装饰器内部重新定义一个函数,将其返回,用来替换被装饰的函数。装饰器内部定义函数几乎都需要闭包

# 设计某商品收盘均价。注:实时增加新价

# 类写法
class Averager():
    def __init__(self):
        self.series = []

    def __call__(self, price):     # 可调用对象
        self.series.append(price)
        print(sum(self.series) / len(self.series))

avg = Averager()
avg(10)  # 可调用对象 __call__()
avg(11)
10.0
10.5
# 函数式写法,高阶函数

def make_averager():
    series = []

    def averager(price):
        series.append(price)
        print(sum(series) / len(series))

    return averager

avg = make_averager()
avg(10)
avg(11)
10.0
10.5

类写法和函数式写法:

  • 共同之处:通过实例化类Averager()和调用make_averager()都会得到一个可调用对象

  • 不同之处:如何存历史值price?类self.series,而函数式则存在series中。

而series作为函数make_averager局部变量,在函数返回时局部变量应该销毁。而series相对于make_averager的内部函数averager称为自由变量。

    series = []

    def averager(price):
        series.append(price)
        print(sum(series) / len(series))

这个代码块称为闭包。闭包是一种函数,它会保留定义函数时存在的自由变量绑定,即使像make_averager函数返回作用域不存在了,averager函数仍然保留使用这些自由变量绑定。

重点:只有在嵌套函数中的函数,才可能处理不在全局作用域中的外部变量,相对该函数称为自由变量。而该函数与自由变量称为闭包。

3. 关键字nonlocal

4. 参数化装饰器 - 装饰器函数工厂

装饰器如何传递参数?通过创建装饰器工厂函数,参数传递给装饰器工厂函数,返回装饰器。

def add_start_docstrings(*docstr):
    def docstring_decorator(fn):
        fn.__doc__ = "".join(docstr) + (fn.__doc__ if fn.__doc__ is not None else "")
        return fn

    return docstring_decorator

@add_start_docstrings("""Bert Model with a `language modeling` head on top. """, BERT_START_DOCSTRING)
def BertForMaskedLM():
    pass

进阶

GrahamDumpleton wrapt

https://github.com/GrahamDumpleton/wrapt/tree/develop/blog



Read more:

Related posts: