博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Python装饰器

Posted on 2020-05-18 22:47  cxysailor  阅读(162)  评论(0)    收藏  举报

Python装饰器1. 变量的作用域2. 高阶函数2.1 高阶函数:变量可以指向函数函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称为高阶函数2.2 构成高阶函数的条件3. 闭包(closure)3.1 定义3.2 通过一个例子来理解闭包的概念3.3 闭包的调用方法3.4 使用闭包的注意事项4. 装饰器4.1 装饰器的作用 - 或者说功能 - 存在的意义4.2 装饰器的用法4.3 装饰器的本质4.3 装饰器的原则 - 开放封闭原则4.4 装饰器执行流程图4.5 装饰器传参数

Python装饰器

1. 变量的作用域

Python变量作用域的规则 - LEGB

  • L - Local(function) 函数内的名字空间,局部变量

  • E - Enclosing function locals 外部嵌套函数的名字空间(如closure) 嵌套变量

  • G - Global(module) 函数定义所在模块(文件)的名字空间 全局变量

  • B - Builtin(Python) Python 内置模块的名字空间 内置变量

变量作用域的顺序为

L ​→ E → G → B; 如果都找不到,程序就会报错

2. 高阶函数

2.1 高阶函数:变量可以指向函数函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称为高阶函数

2.2 构成高阶函数的条件

满足下列2个条件之一的,即构成高阶函数:

  • 函数名可以作为参数输入

  • 函数名可以作为返回值

一个简单的高阶函数

 
1 def add(x, y, f):
2      return f(x) + f(y)

 

调用并执行add()函数

  1 print(add(-5, 6, abs)) # 把内置函数abs()作为参数传递给函数add() 

输出结果为

  1 11 # 想当于abs(-5) + abs(6) = 5 + 6 = 11 

3. 闭包(closure)

3.1 定义

如果在一个内部函数里面,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)

3.2 通过一个例子来理解闭包的概念

 
1 def funX(x):  # 外部函数
2      def funY():  # 内部函数- 即"闭包"
3          return x * y  # 引用外部函数的变量x
4      return funY

 

  • 如果一个内部函数(funY()就是这个内部函数)里面,

  • 外部作用域(但不是在全局作用域)的变量进行引用(x就是被引用的变量,它在外部作用域funX()函数里面,但不在全局作用域里)

  • 则这个内部函数funY()就是一个闭包

3.3 闭包的调用方法

  1. 方法一

     
    1 i = funX(8)
    2  i(5)
    3 4  # 输出结果为:
    5  40  # 即 8 x = 40

     

     
  2. 方法二

     
    1 funX(8)(5)
    2 3  # 输出结果为:
    4  40

     

     

3.4 使用闭包的注意事项

  1. 由于闭包的概念就是由内部函数而来,所以不能在外部函数以外的地方对内部函数进行调用。比如下面的做法就是错误的

     
    1 funY(5)
    2 3  # 会输出如下的错误:
    4  ------------------------------------------------------------------------
    5  NameError                             Traceback (most recent call last)
    6  <ipython-input-15-e150cf4e43a4> in <module>
    7  ----> 1 inner(5)
    8 9  NameError: name 'inner' is not defined

     

     

  2. 闭包中,外部函数的局部变量对应对应内部函数的局部变量,相当于全局变量与局部变量的对应关系,在内部函数中只能对外部函数的局部变量进行访问,但不能进行修改

     1  def funX():
     2  x = 5
     3  def funY():
     4  x *= x
     5  return x
     6  return funY
     7  # 调用这个函数
     8  funX()()
     9  # 时就会报错:
    10  -----------------------------------------------------------------------
    11  UnboundLocalError                      Traceback (most recent call last)
    12  <ipython-input-17-d6a06e4f3d3e> in <module>
    13  ----> 1 funX()()
    14  
    15  <ipython-input-16-93ad23d68238> in funY()
    16        2     x = 5
    17        3     def funY():
    18  ----> 4         x *= x
    19        5         return x
    20        6     return funY
    21  
    22  UnboundLocalError: local variable 'x' referenced before assignment

     

    这个报错信息与在内部改变全局变量时的错误基本一样,Python认为在内部函数的x是局部变量的时候,外部函数的变量x就被屏蔽了起来,所以执行x *= x语句时,在右边根本就找不到局部变量x的值,因此报错。

  3. 在Python3中,如果希望内部函数里可以修改外部函数里的局部变量的值,可以使用关键字nonlocal,使用方式与global一样:

     1 def funX():
     2         x = 5
     3         def funY():
     4             nonlocal x
     5             x *= x
     6             return x
     7         return funY
     8     # 调用函数
     9     funX()()
    10     # 就可以正常输出结果:
    11     25

     

     

4. 装饰器

4.1 装饰器的作用 - 或者说功能 - 存在的意义

不想修改函数的调用方式,但是还想在原来的函数前后添加功能

装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处就在于它的返回值也是一个函数。使用装饰器的好处是在不用更改原函数的代码的前提下给函数增加新的功能。实际工作中,有些时候核心代码不可以直接修改,这样在不改动原代码的情况下,可以再定义一个函数。但是生效需要再次执行函数。

4.2 装饰器的用法

@装饰器函数名(装饰函数)

被装饰的函数(即原函数)

装饰器放在一个函数开始定义的上方,就像一顶帽子一样戴在这个函数的头上,和这个函数绑定在一起。在调用帽子下的这个函数时,第一件事不是执行这个函数,而是将帽子下这个函数作为参数传入头顶上这个帽子函数(装饰器函数),这个帽子被称为装饰函数或装饰器

4.3 装饰器的本质

闭包函数

装饰器符@类似于回调函数,函数A把函数B作为自己函数的形参,在函数B执行前执行一些自己的操作。比如:计数、打印、提示信息等...然后返回目的函数

4.3 装饰器的原则 - 开放封闭原则

开放 - 对功能拓展是开放的

封闭 - 对修改是封闭的

4.4 装饰器执行流程图

decorator in python 

执行流程:

  • 程序从上到下一次执行

  • 遇到3个函数(show_time/is_prime/prime_num)都不会执行

  • 继续向下执行到第42行代码c = prime_num(10000),调用函数prime_num()

  • 由于函数prime_num()被加了装饰器@show_time,所以程序不会进入该函数,而是进入装饰器函数show_time中执行其中的代码

  • 当程序执行到第12行result = func(*args)时,程序才开始进入第33行的函数prime_num()中执行其中的代码

  • 程序执行到第37行时,调用了函数is_prime(),从而进入这个函数执行代码,完成后返回到第37行,继续向下执行

  • 函数prime_num()执行完成,程序返回到第12行,继续向下执行到函数show_time完成,将结果返回给第42行的函数调用。

4.5 装饰器传参数

装饰器传参数可以实现装饰器内的某些功能成为可以选择的。

要实现这样的功能需要嵌套3层函数,即在原来的装饰器函数外面再加一个函数(带参数):

 1 def log(flag):  # 可以传入参数的装饰器函数
 2     def show_time(func):
 3         """作为装饰器的函数-计算程序执行的时间"""
 4         def wrapper(args):
 5             """
 6             :param args: 传入的函数不管有几个,一概接收
 7             """
 8             start = time.time()
 9             result = func(args)  # 执行函数prime_num
10             end = time.time()
11             print('\nTime used:{:.4}s'.format(end - start))
12             if flag == 't':
13                 print('日志记录...')  # 添加了一个打印日志的功能
14             return result
15         return wrapper
16     return show_time
17 
18 
19 @log('t')  # 添加装饰器并传入参数
20 def prime_num(maximum):
21     """添加了装饰器的函数-统计有多少个质数"""
22     count = 0
23     for n in range(2, maximun):
24         if is_prime(n):
25             count += 1
26     return count