装饰器的知识准备


  • 函数,函数参数
  • 作用域: 全局变量,局部变量
  • 变量解析规则:LEGB法则 - 假设嵌套函数(第二层函数),解析器查找内部函数的变量的顺序如下。 在任何一层先找到了符合要求的变量,则不再向外查找。如果没有,则抛出N
    1. Local - 本地函数内部,通过任何方式赋值的,而且没有被global关键字声明为全局变量的变量
    2. Enclosing - 直接该内部函数的外围空间(即它的上层函数)的本地作用域。多层嵌套,则有内而外逐层查找,直至最外层的函数
    3. Global - 全局空间(模块enclosed.py), 在顶层赋值的变量
    4. Buildin - 内置模块(__buildin__) 中预定义的变量名中查找变量。 
  • 变量生存周期:局部变量的生存周期随着函数的调用而存在,随着函数的结束而消亡。
  • 嵌套函数:函数中套函数并调用。
  • 高阶函数: 一个函数接受另一函数作为变量。 函数即变量
  • Python中一切皆对象(objects,之后会讲到面对对象编程的问题)。 当定义一个函数时,函数第一类对象/一级类对象。 所谓第一类对象,意思就是可以用标识符对对象命名,并且对象可以当作数据处理。例如赋值,作为参数传递给函数,或者作为返回值return等。 
  • 函数对象 vs. 函数调用 (非常容易搞混)
def func():
    return "hello,world"

ref1 = func   # 将函数对象赋值给ref1 , type(ref1)是 function 
ref2 = func()  # 函数调用, type(ref2) 是 string
  • 闭包: 装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。所以理解闭包概念很重要。 所谓闭包,就是将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象。总结:
    • 闭包最重要的使用价值:封存函数执行的上下文环境;
    • 闭包在其捕捉的执行环境(def语句快所在上下文)中,也遵循LEGB规则逐层查找,直至符合要求的变量,或者抛出异常

装饰器


概念:

定义:装饰器的“器“就是函数,基本语法用def 来定义,本质就是函数,其功能是装饰其他函数,为其他函数添加附加功能

原则:装饰器对被装饰函数完全透明

  1. 甭能修改被装饰函数的源代码
  2. 不能修改被装饰函数的调用方式

结构:高阶函数 + 嵌套函数;

  1. 函数 即“变量“
  2. 高阶函数嵌套函数
    1. 把一个函数名当作实参传入另一个函数 ------ 在其不修改被装饰函数源代码的情况下为其添加功能
    2. 返回值中包含函数名 ----- 不修改函数的调用方式

总结下, 装饰器就是一个返回值为函数的高阶函数,其中至少嵌套一个函数(作为返回值返回)

工作原理:Func = Deco(Func)  用语法糖 syntax suger来表示@

第一步: 被装饰的函数作为参数传递给装饰器函数,并执行装饰器函数, 返回值记作Newfunc

第二步: 原函数重新赋值为Newfunc

课堂案例 - 如何构建装饰器

  • 最简单装饰器: Moduel 2 - Vedio 7 - 装饰器的小高潮。
 1 #!user/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 import time
 5 
 6 
 7 def timer(func):    # timer(test1), 将test1的内存地址传给func; 其实最终通过return返回deco的内存地址
 8     def deco():
 9         start_time = time.time()
10         func()
11         stop_time = time.time()
12         print('the func run time is %s' %(stop_time-start_time))
13     return deco   # 直接返回deco函数的内存地址,如此下面test1 = deco(test1),如此test1()就可以正常调用
14 
15 
16 @timer       # 就是一步运行工作,等于test1 = timer(test1) 注意此处不能加括号,因为装饰的函数,而test1()是一个返回值
17 def test1():
18     time.sleep(3)
19     print('in the test1')
20 
21 
22 test1()              # 此时test1 执行的是deco的内存地址,因为timer函数中的return deco
23 print(test1)         # 被装饰过的test1的内存地址返回: <function timer.<locals>.deco at 0x00000244D3824048>
View Code

逻辑解释

起始行 结束行 代码 解释
   - 4 import time  导入time模块
4 7 def timer(func)  timer(test1), 将test1的内存地址闯入func, 最终return deco 的内存地址
7 16  @timer

直接跳到语法糖,执行timer装饰器

表示: test1 = timer(test1)

16 8 def deco()  
8 13   return deco 返回值是deco的内存地址
13 22 test1() 调用test1(),经过@timer的重新赋值,此时执行的是deco的内存地址,最终调用deco()
22 9 start_time = time.time() 执行deco()    
9 10 func() 在deco函数内部,开始调用变量func, 此时,func即test1(),   
10 18 time.sleep(3)   执行原test1()  
18  19 print('in the test1)  
19 11   stop_time = time.time()  
11 12 print('run time = %s' %(stop_time - start_time)  

核心是内存地址的转移。

  • 被装饰的函数带参数
#!user/bin/env python
# -*- coding:utf-8 -*-

import time


def timer(func):    # 将test2的内存地址传给func
    def deco(*args,**kwargs):   # 用非固定参数*args, **kwargs;如此满足有参数,和没有参数的被装饰函数
        start_time = time.time()
        func(*args,**kwargs) 
        stop_time = time.time()
        print('the func run time is %s' %(stop_time-start_time))
    return deco   # 直接返回deco函数的内存地址

@timer         # test2 = timer(test2)= deco; test2() = deco(); 所以当test2有函数变量,deco也需要加函数变量。
def test2(name):  # test2本身带参数
    time.sleep(3)
    print('in the test2')

test2('alex')
View Code

在这种情况下,

    1. 被装饰函数test2自带参数的情况下, 其对应装饰器中最终换回的内存地址的函数deco()也应带有函数。
    2. 因为装饰器最终要用于不同的被装饰函数,对于deco()的参数应用 非固定参数 *args 和 **kwargs。
  • 被装饰的函数有返回值:由于home() = wrapper(home),最终装饰过的home()返回为wrapper的内存地址的调用。 原函数的return数据也要在wrapper中写入。
 1 #!user/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 user,passwd = 'alex','abc123'
 5 
 6 def auth(func):
 7     def wrapper(*args,**kwargs):
 8         username = input('Username').strip()
 9         password = input('Password').strip()
10 
11         if user == username and passwd == password:
12             print("\033[32;1mUser has passed authorization\033[0m")
13             res = func(*args,**kwargs)    # 赋值
14             return res                              # 返回res
15         else:
16             exit('\033[31;1mInvalid username or password\033[0m')
17     return wrapper    # 返回wrapper的内存地址
18 
19 @auth(auth_type="local")
20 def home():
21     print('welcome to home page')
22     return "from home"
23 
24 print(home())   #打印返回from home 
view code
  • 带参数的装饰器(最终版装饰器):如果装饰器带参数,被装饰的函数有参数,那么装饰器将会有三层。
 1 #!user/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 # 例三:带参数的装饰器(终结版装饰器)
 5 # 情景要求: 配用多种认证方式。 home()认证方式用本地local认证,bbs()用远程的ldap
 6 
 7 user,passwd = 'alex','abc123'
 8 
 9 
10 def auth(auth_type):   # home()的can
11     def outer_wrapper(func):
12         def wrapper(*args,**kwargs):
13             username = input('Username').strip()
14             password = input('Password').strip()
15 
16             if user == username and passwd == password:
17                 print("\033[32;1mUser has passed authorization\033[0m")
18                 res = func(*args,**kwargs)    # 赋值
19                 return res                   # 返回res
20             else:
21                 exit('\033[31;1mInvalid username or password\033[0m')
22         return wrapper    # 返回wrapper的内存地址
23     return outer_wrapper
24 
25 def index():
26     print('welcome to index page')
27 
28 
29 @auth(auth_type="local")
30 def home():
31     print('welcome to home page')
32     return "from home"
33 
34 home()
35 
36 @auth(auth_type="ldap")
37 def bbs():
38     print('welcome to bbs page')
39 
40 bbs()
View Code


Reference:

“Python 里为什么函数可以返回一个函数内部定义的函数?”,  https://www.zhihu.com/question/25950466/answer/31731502

“12步轻松搞定python装饰器”,http://python.jobbole.com/81683/

 

posted on 2017-08-03 18:44  lg100_lg100  阅读(158)  评论(0编辑  收藏  举报