# 装饰器的语法糖本质

# 装饰器的语法糖本质

有装饰器被装饰函数如下:

def log_call(func):
    def proxy(*args, **kwargs):
        logging.info('begin call: {name}'.format(name=func.__name__))
        result = func(*args, **kwargs)
        logging.info('call done: {name}'.format(name=func.__name__))
        return result
    return proxy

@log_call
def work_bar(data):
    pass

对其进行字节码编译可获得如下结果:

  2           0 LOAD_NAME                0 (log_call)
              2 LOAD_CONST               0 (<code object work_bar at 0x100cf2d20, file "<dis>", line 2>)
              4 LOAD_CONST               1 ('work_bar')
              6 MAKE_FUNCTION            0
              8 CALL_FUNCTION            1
             10 STORE_NAME               1 (work_bar)
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

注意第8条字节码-CALL_FUNCTION 实际就是把246生成的函数作为参数传递给log_call函数调用。

可以发现实质上@log_call​​ 这行代码的作用只是告诉 Python 编译器,在函数定义后面插入代码 work_bar = log_call(work_bar)​​。

image

  1. 第一条字节码将 log_call 函数加载进当前执行栈栈顶;
  2. 第二、三条字节码将 work_bar 代码对象和 work_bar 函数名加载到栈顶,为创建 work_bar 函数做好准备;
  3. 第四条字节码完成 work_bar 函数创建,该字节码执行完毕后,work_bar 函数便位于栈顶;
  4. 第五条字节码则以 work_bar 为参数调用 log_call 函数,并将 log_call 返回的 proxy 函数保存于栈顶;
  5. 接下来的 STORE_NAME 从栈顶取出 proxy 函数并保存到当前局部名字空间,它一般也是模块的属性空间;