谈谈装饰器(Decorator)的实现原理

栏目: Python · 发布时间: 5年前

内容简介:熟悉在要理解

熟悉 Java 编程的程序猿对 装饰器模式 一定不陌生,它是能够动态的给一个类添加新的行为的一种设计模式。相对于通过继承的方式使用装饰器会更加灵活。

谈谈装饰器(Decorator)的实现原理

Python 里面装饰器( Decorator )也是一个非常重要的概念。跟装饰器模式类似,它 能够动态为一个函数、方法或者类添加新的行为 ,而不需要通过子类继承或直接修改函数的代码来获取新的行为能力,使用 Decorator 的方式会更加 Pythonic

要理解 装饰器 我们就要从函数说起。

0x00 函数

Python 中函数是作为一级对象存在的(一切都是对象),它拥有自己的属性,函数名可以赋值给一个变量,也可以作为另一个函数的参数进行传递。

1、定义函数

def fib(n):
    """打印小于 n 的 fibonacci 数列"""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

def call_fib(func):
    """函数名作为函数的参数进行传递"""
    func(1000)

if __name__ == '__main__':
    print(fib)  # <function fib at 0x103e66378>
    print(isinstance(fib, object))  # 函数是一级对象:True
    print(fib.__name__)  # 函数名称:fib
    print(fib.__code__.co_varnames)  # __code__属性是函数中的'代码对象',co_varnames是函数中的本地变量,以元组的方式展现:('n', 'a', 'b')
    print(fib.__doc__)  # 函数中的注释 
    print(fib.__globals__)  # 全局变量
    
    f = fib  # 函数赋值给一个变量f
    f(1000)  # 通过变量f对函数fib调用
    
    call_fib(fib) # 函数名作为参数
复制代码

2、嵌套函数

在定义函数时,在函数内部定义一个函数。

def outer_func():
    # 在函数内部定义一个函数
    def inner_func():
        print('inner func')

    inner_func()
    print('outer func')
复制代码

嵌套函数对我们理解装饰器非常重要,也是闭包实现的基础,这里先引出了本地变量和全局变量的概念,后文会详细说明闭包的概念。

3、全局变量( globals )和本地变量( locals )

根据 作用域 的不同,变量可以分成全局变量和本地变量,这其实是相对的概念。例如在下面的模块中 gvar 是一个全局变量,而在函数 outer_func() 定义的是本地变量。

gvar = 'global var' # 全局变量


def outer_func():
    gvar = 'outer var' # outer_func 本地变量

    # 在函数内部定义一个函数
    def inner_func():
        gvar = 'inner var' # inner_func 本地变量
        print('inner: {}'.format(gvar))

    inner_func()
    print('outer: {}'.format(gvar))

outer_func()
print('gvar in global : {}'.format(gvar))
# 输出结果
# inner: inner var
# outer: outer var
# gvar in global : global var

复制代码

在函数外定义了 全局变量 gvar ,同时在 outer_func() 函数中也定义了 gvar ,而这个是 本地变量

从示例代码中可以看到, outer_func() 并没有改变全局变量的值。

在函数中定义的变量都存储在 本地符号表 ( local symbol table )里,同样 inner_func() 中的 gvar 也存在它自己的本地符号表中,而全局变量gvar是则存储在 全局符号表 ( global symbol table )。

变量的查找路是: 首先从本地符号表中查找,然后是外部函数(在嵌套函数中)的本地符号表中查找,再到全局符号表,最后是内置符号表

graph TD
A[本地符号表]-->B[外部函数的本地符号表]
B[函数外的本地符号表]-->C[全局符号表]
C[全局符号表]-->D[内置符号表]
复制代码

如果把上面代码中的 inner_func() 中的 gvar = 'inner var' 注释掉,那么输出的结果将是

# inner: outer gvar # inner_func中引用的gvar变量是outer_func中定义的
# outer: outer gvar
# gvar in global : global var
复制代码

变量查找逻辑可以简单理解为: 就近查找 。 如果在以上路径中都没有找到, Python 解析器将抛出 NameError: name 'gvar' is not defined

若在函数中要使用全局变量,那么就需要用到 global 关键字。 对上文的代码修改如下

gvar = 'global var'


def outer_func():
    global gvar  # 声明gvar是全局变量
    gvar = 'outer gvar'

    # 在函数内部定义一个函数
    def inner_func():
        gvar = 'inner gvar'  # 这个依然是本地变量
        print('inner: {}'.format(gvar))

    inner_func()
    print('outer: {}'.format(gvar))

outer_func()
print('gvar in global : {}'.format(gvar))

# 输出结果
# inner: inner gvar
# outer: outer gvar
# gvar in global : outer gvar
复制代码

除了 global 还有一个 nonlocal 的关键字,它的作用是在函数中使用外部函数的变量定义(注意:不能是全局变量)

gvar = 'global var' # 全局变量


def outer_func():
    gvar = 'outer gvar' # 本地变量
    # 在函数内部定义一个函数
    def inner_func():
        nonlocal gvar  # nonlocal的含义是让gvar使用外部函数的变量,
        # 如果外部函数没有定义该变量,那么运行时将抛出SyntaxError: no binding for nonlocal 'gvar' found
        gvar = 'inner gvar'  # 这个依然是本地变量
        print('inner: {}'.format(gvar))

    inner_func()
    print('outer: {}'.format(gvar))

# 输出结果
# inner: inner gvar
# outer: inner gvar
# gvar in global : global var
复制代码

inner_func() 中使用 nonlocal 关键字声明的 gvar 必须在外部函数(即 outer_func() 函数)定义,否则将抛出 SyntaxError: no binding for nonlocal 'gvar' found

0x01 什么是闭包

首先结合前文的 嵌套函数定义 的例子,修改一下代码,返回内部函数的对象。

# 普通的嵌套函数
def outer_func():
    # 在函数内部定义一个函数
    def inner_func():
        print('inner func')

    inner_func()
    print('outer func')


# 闭包
def closure_func():
    local_var = 100

    def inner_func():
        print('inner func call : {}'.format(local_var))

    return inner_func # 这里将形成一个闭包

f = closure_func()
print(f)
print(f.__closure__)
print(outer_func)
print(outer_func.__closure__)


# 输出结果
# <function closure_func.<locals>.inner_func at 0x104f8a8c8> 
# (<cell at 0x104f6fa68: int object at 0x104d23910>,)
# <function outer_func at 0x1070ea268>
# None # 普通函数的__closure__属性为空
复制代码

可以看出变量 f 就是闭包,它 是一个函数对象 ,这个函数 可以持有本地变量 local_var ,而这个 本地变量可以脱离定义它的作用域而存在

现在来维基百科关于闭包的定义

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。--引用自维基百科

0x02 实现装饰器

有了前面的铺垫,理解装饰器就非常简单啦。 一个函数返回另外一个函数,通常会使用 @wrapper 的语法形式,而装饰器其实就是一种语法糖( syntactic sugar )。

我们还是看代码

# 定义一个logger函数,在函数执行前打印log信息
def logger(func):
    def log_func(*args):
        print('Running "{}" with arguments {}'.format(func.__name__, args))
        return func(*args)

    return log_func # 形成闭包

# 定义加法函数
def add(x, y):
    return x + y

# 以下两种方式的使用是等价的,当然使用@logger更加Pythonic
@logger
def add(x, y):
    return x + y


# add = logger(add)

print(add(1,4))

# 输出结果
# Running "add" with arguments (1, 4)
# 5
复制代码

这样的通过自定义装饰器,我们就可以动态地给函数添加新的功能。

除了自定义的装饰器,还有常见的如 classmethod()staticmethod() 内置的装饰器。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程语言实现模式

编程语言实现模式

Terence Parr / 李袁奎、尧飘海 / 华中科技大学出版社 / 2012-3-20 / 72.00元

《编程语言实现模式》旨在传授开发语言应用(工具)的经验和理念,帮助读者构建自己的语言应用。这里的语言应用并非特指用编译器或解释器实现编程语言,而是泛指任何处理、分析、翻译输入文件的程序,比如配置文件读取器、数据读取器、模型驱动的代码生成器、源码到源码的翻译器、源码分析工具、解释器,以及诸如此类的工具。为此,作者举例讲解已有语言应用的工作机制,拆解、归纳出31种易于理解且常用的设计模式(每种都包括通......一起来看看 《编程语言实现模式》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码