谈谈装饰器(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() 内置的装饰器。


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

查看所有标签

猜你喜欢:

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

Java Concurrency in Practice

Java Concurrency in Practice

Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes、Doug Lea / Addison-Wesley Professional / 2006-5-19 / USD 59.99

This book covers: Basic concepts of concurrency and thread safety Techniques for building and composing thread-safe classes Using the concurrency building blocks in java.util.concurrent Pe......一起来看看 《Java Concurrency in Practice》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具