函数装饰器和闭包(一)

装饰器基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后将它返回,或者将其替换成另一个函数或可调用对象

def deco(func):
    def inner():
        print("running innner()")

    return inner  # <1>


@deco
def target():  # <2>
    print("running target()")


target()  # <3>

  

运行结果:

running innner()

  

  1. deco返回inner函数对象
  2. 使用deco装饰target函数
  3. 调用被装饰的target函数其实执行的是inner函数

 

Python装饰器何时执行装饰器

装饰器的一个关键特性是,它在被装饰的函数定义之后立即执行,通常在导入模块或文件时

registry = []  # <1>


def register(func):  # <2>
    print('running register(%s)' % func)
    registry.append(func)
    return func


@register  # <3>
def f1():
    print('running f1()')


@register  # <4>
def f2():
    print('running f2()')


def f3():  # <5>
    print('running f3()')


def main():  # <6>
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()


if __name__ == '__main__':
    main()

  

  1. 定义一个名为registry的列表
  2. register函数接收一个函数对象,然后打印这个函数对象,再将函数对象添加到registry列表中,最后返回函数对象
  3. f1函数被register函数装饰
  4. f2函数被register函数装饰
  5. f3没有被register函数装饰
  6. 打印registry列表并依次执行f1、f2、f3三个函数

如果不看装饰器,应该先打印running main(),再打印registry列表,之后依次执行三个函数,三个函数会打印其函数中的内容,但真实情况是如何呢?我们看一下运行结果:

running register(<function f1 at 0x000000773FBF0730>)
running register(<function f2 at 0x000000773FBF38C8>)
running main()
registry -> [<function f1 at 0x000000773FBF0730>, <function f2 at 0x000000773FBF38C8>]
running f1()
running f2()
running f3()

  

很遗憾,运行结果和我们一开始的设想不一样,我们看到程序是优先执行register函数,将f1和f2两个函数当做参数传入register函数中,然后register函数打印其传入的函数,之后才打印running main(),再来打印registry列表,这时候registry列表已经不是一个空列表了,在register函数中会把传入的函数参数加入到registry列表中,因为f1和f2被register函数装饰,所以列表有两个函数对象,之后才是依次打印3个函数中的内容

 因此我们可以知道,函数装饰器在导入模块时就立即执行,而被装饰的函数只有在明确调用时才运行。事实上,装饰器返回的函数,与通过参数传入所返回的结果相同,大部分装饰器会在内部定义一个函数,然后将其返回

装饰器是一项很有用的技术,很多Python Web框架使用装饰器装饰一个函数,将函数添加到某中央注册处,当客户端发起一个HTTP请求时,通过URL查找相对应的函数,执行函数返回结果

变量作用域规则

我们先来看三个例子:

 

>>> def f1(a):
...     print(a)
...     print(b)
...
>>> f1(3)
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f1
NameError: name 'b' is not defined

毋庸置疑,b变量没有被定义过,所以f1在打印b的时候报出为定义b的错误

 

>>> b = 6
>>> def f2(a):
...     print(a)
...     print(b)
...
>>> f2(3)
3
6 

我们在函数体之外定义了变量b,这里正常打印了b的值

 

>>> def f3(a):
...     print(a)
...     print(b)
...     b = 9
...
>>> f3(3)
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f3
UnboundLocalError: local variable 'b' referenced before assignment  

这里输出了3,证明print(a)执行了,但在执行print(b)的时候报错了。是什么原因造成第三个例子无法正常打印b呢?原因是,Python在编译函数的定义体时,它判断b是局部变量,因为在函数中给b赋值了。在执行函数体的时候,Python会尝试从本地环境中获取变量b,但这个时候发现b还没有绑定值

如果想让解释器把b当成全局变量,需要使用global声明:

>>> def f4(a):
...     global b
...     print(a)
...     print(b)
...     b = 9
...
>>> f4(3)
3
6

  

dis模块会打印Python函数字节码,我们比较一下之前例子的字节码

第一个例子:

>>> from dis import dis
>>> dis(f1)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_GLOBAL              1 (b)
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

  

  • 第三行,获取全局变量名称print
  • 第四行,从本地环境中获取变量a
  • 第九行,从全局变量中获取b

在第一个例子中,不管在函数体或是函数外都没定义b变量,所以获取b变量肯定会报错

而第二个例子中,我们在函数外围定义了变量b,所以第二个函数字节码与第一个类似,但可以正常执行

再来看第三个例子:

>>> b = 6
>>> def f3(a):
...     print(a)
...     print(b)
...     b = 9
...
>>> from dis import dis
>>> dis(f3)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_FAST                1 (b)
             12 CALL_FUNCTION            1
             14 POP_TOP

  4          16 LOAD_CONST               1 (9)
             18 STORE_FAST               1 (b)
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE

  

第三个例子也是执行失败的例子,我们看第十五行,这里获取变量b并不是LOAD_GLOBAL 从全局环境中获取,而是LOAD_FAST从本地环境获取,这个时候b还未赋值,所以当解释器发现b没有绑定值,就会报出错误

 

posted @ 2018-07-14 09:53  北洛  阅读(208)  评论(0编辑  收藏  举报