函数装饰器和闭包(一)

装饰器基础知识

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

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没有绑定值,就会报出错误

原文地址:https://www.cnblogs.com/beiluowuzheng/p/9308382.html

时间: 2024-08-08 19:57:58

函数装饰器和闭包(一)的相关文章

函数装饰器和闭包(四)

上一章:函数装饰器和闭包(三) 单分派函数 假设我们现在要开发一个函数,这个函数可以传入一个元素,函数要判断元素的类型,再将其打印出来 from collections.abc import MutableSequence, MutableMapping def print_item(item): if isinstance(item, int): print("int:", item) elif isinstance(item, MutableSequence): print(&qu

Python 函数装饰器和闭包

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 25.0px Helvetica } 装饰器基础知识 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象. p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 假如有个名为 decorate 的装饰器: @decorate def targ

函数嵌套 ,名称空间与作用域 ,闭包函数 ,装饰器 ,迭代器, 生成器 三元表达式,列表解析,生成器表达式 递归与二分法, 内置函数

函数嵌套名称空间与作用域闭包函数装饰器迭代器生成器三元表达式,列表解析,生成器表达式递归与二分法内置函数--------------------------------------------函数的嵌套调用:在调用一个函数的过程中,又调用了其他函数函数的嵌套定义:在一个函数的内部,又定义另外一个函数def max(x,y): if x>y: return x else: return ydef max1(a,b,c,d): res=max(a,b) res2=max(res,c) res3=ma

Python 函数对象 命名空间与作用域 闭包函数 装饰器 迭代器 内置函数

一.函数对象 函数(Function)作为程序语言中不可或缺的一部分,但函数作为第一类对象(First-Class Object)却是 Python 函数的一大特性. 那到底什么是第一类对象(First-Class Object)呢? 在 Python 中万物皆为对象,函数也不例外,函数作为对象可以赋值给一个变量.可以作为元素添加到集合对象中.可作为参数值传递给其它函数,还可以当做函数的返回值,这些特性就是第一类对象所特有的. 1.函数身为一个对象,拥有对象模型的三个通用属性:id.类型.和值.

python函数下篇装饰器和闭包,外加作用域

装饰器和闭包的基础概念 装饰器是一种设计模式能实现代码重用,经常用于查日志,性能测试,事务处理等,抽离函数大量不必的功能. 装饰器:1.装饰器本身是一个函数,用于装饰其它函数:2.功能:增强被装饰函数的功能. 装饰器需要遵循的原则 1.不修改被装饰函数的源代码(开放封闭原则) 2.为被装饰函数添加新功能后,不修改被装饰函数的调用方式 装饰器 = 高阶函数+函数嵌套+闭包 高阶函数 1.函数接受的参数是一个函数名 2.函数的返回值是一个函数名 3.只有上述条件满足一个就是高阶函数 def foo(

【Python 函数对象 命名空间与作用域 闭包函数 装饰器 迭代器 内置函数】

一.函数对象 函数(Function)作为程序语言中不可或缺的一部分,但函数作为第一类对象(First-Class Object)却是 Python 函数的一大特性. 那到底什么是第一类对象(First-Class Object)呢? 在 Python 中万物皆为对象,函数也不例外,函数作为对象可以赋值给一个变量.可以作为元素添加到集合对象中.可作为参数值传递给其它函数,还可以当做函数的返回值,这些特性就是第一类对象所特有的. 1.函数身为一个对象,拥有对象模型的三个通用属性:id.类型.和值.

python函数装饰器

学习装饰器前提需要了解高阶函数,函数嵌套,函数闭包 python函数装饰器,顾名思义就是装饰函数,为函数添加新功能的的一种方式. 为什么要使用装饰器呢? 因为函数在运行时,如果不使用装饰器对函数进行功能添加,需要修改函数源代码,这样修改无疑会增加程序的冗余和复杂性,也不便于程序员对其进行修改.使用装饰器,可以在不改变函数源代码和调用方式的前提下,使用语法糖@装饰器,对函数功能进行添加. 装饰器本质上就是一个函数. 我们使用一个简单的例子来实现: import time #这是一个装饰器函数名为t

Python 函数装饰器入门

原文链接: --> A guide to Python's function decorators Python功能强劲,语法表现力强,尤其装饰器深深的吸引着我.在设计模式中,装饰器可以在不使用子类的情况下,动态的改变函数,方法以及类的功能.这个功能非常有用,特别在你想扩展函数的功能同时又不想改变原有的函数.的确,我们任意的实现装饰器设计模式,但是,python通过提供简单的语法和特性让装饰器的实现变的如此简单. 在本文中,我将用一组例子来深入浅入python 函数装饰器的功能,所有的例子都是在

Python高阶函数与函数装饰器-day4

上节回顾 高阶函数 闭包函数 函数装饰器 模块导入 一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首先是Python3-->代码文件都是用utf-8来解释的.将代码和文件读到内存中就变成了Unicode,这也就是为什么Python只有encode没有decode了,因为内存中都将字符编码变成了Unicode,而Unicode是万国码,可以"