zoukankan      html  css  js  c++  java
  • Python基础-装饰器

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码。装饰器不光能装饰函数,也能装饰其他的对象,比如类。

    开放封闭原则:规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即,封闭:已实现的功能代码块;开放:对扩展开放。

    语法:

    def wrapper(func):    #func接收被装饰函数的函数名‘foo’
        def result():
            print('before')   #增加被装饰函数执行前的功能
            res = func()      #执行被装饰函数‘foo()’
            print('after')     #增加被装饰函数执行后的功能
            return res
        return result
    
    
    @wrapper        #将扩展的功能使用‘@’语法糖写在被装饰函数的上方
    def foo():
        print('foo')
    
    foo()
    

      

    上述实例解读:

    1. 程序开始运行,从上往下编译,读到def wrapper(func):的时候,发现这是个“一等公民”->函数,于是把函数体加载到内存里,然后过。  

    2. 读到@wrapper的时候,程序被@这个语法糖吸引住了,知道这是个装饰器,按规矩要立即执行的,于是程序开始运行@后面那个名字wrapper所定义的函数。(@wrapper只能放在被装饰的函数的上方最近处,不要空行。)

    3. 程序返回到wrapperr函数,开始执行装饰器的语法规则,这部分规则是定死的。规则是:被装饰的函数的名字会被当作参数传递给装饰函数。装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。

    • @wrapper和@wrapper()有区别,没有括号时,wrapper函数依然会被执行,这和传统的用括号才能调用函数不同,需要特别注意!有括号时,就可以给装饰器传递参数
    • 是foo这个函数名(而不是函数foo()的返回值)当做参数传递给装饰函数wrapper,也就是:func = foo,@wrapper等于wrapper(foo),实际上传递了foo的函数体,而不是执行foo后的返回值。
    • wrapper函数return的是result这个函数名,而不是result()这样被调用后的返回值。

    4. 程序开始执行wrapper函数内部的内容,一开始它又碰到了一个函数,result函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是潜规则)。

    5. 再往下,碰到return res,返回值是个函数名,并且这个函数名会被赋值给foo这个被装饰的函数,也就是foo = res。

    6. 至此,当调用foo函数时,首先执行的时result函数的代码,在本例中,首先打印‘before’,然后执行func,也就是被装饰函数foo,并将返回值赋给res变量,然后继续执行result函数,打印‘after’。最后返回res。

    以上流程走完,既没有修改foo程序代码,也没有更改其调用方式,就实现了在执行foo前后增加功能的需求。

    疑问:为什么要定义2个函数(wrapper和result),一层函数不行吗? 

    如果只有一层函数,执行到@wrapper时,会自动执行wrapper内部的代码,如果不封装一下,在foo函数未调用时就执行了foo,这与需求不符。

    装饰器的参数传递:

    被装饰函数有一个参数:

    def wrapper(func):    #func接收被装饰函数的函数名‘foo’
        def result(name):
            print('before')   #增加被装饰函数执行前的功能
            res = func()      #执行被装饰函数‘foo()’
            print('after')     #增加被装饰函数执行后的功能
            return res
        return result
    
    
    @wrapper        #将扩展的功能使用‘@’语法糖写在被装饰函数的上方
    def foo(name):
        print(name)
    
    foo('jack')
    

    被装饰函数有多个参数:

    一个函数被多个函数装饰:

      

    装饰器实例

    程序需求:

    1. 在不改变func_1函数(程序)定义和调用方式的基础上,添加计时和用户认证功能
    2. 用户认证要求:
    • 用户最多尝试3次登陆
    • 当存在的用户登陆失败3次后,锁定该用户,限制登陆

    程序代码:

     1 import time
     2 
     3 
     4 def run_timer(func):        #计时器函数
     5     def wrapper(*args, **kwargs):       #装饰器
     6         start = time.time()     #开始计时
     7         func(*args, **kwargs)       #运行程序
     8         end = time.time()       #停止计时
     9         print('Run time is %ss' % (end - start))        #打印程序运行时长
    10 
    11     return wrapper
    12 
    13 
    14 def identiy():
    15     """用户登陆验证程序
    16         最多可尝试3次登陆
    17         当某一存在的用户输入错误密码3次后,锁定该用户,限制登陆"""
    18     with open('account_bak', 'r+') as f_account, open('locked_list', 'a+') as f_locked:
    19         f = 0               #程序返回值变量
    20         l = []              #被锁定用户列表
    21         user_input = []     #输入错误密码的用户名列表
    22         count = 0           # 登陆次数计数器
    23         flag = True         # 登陆循环控制开关
    24         while flag and count < 3:
    25             name = input('Please input username:')  # 输入用户名
    26             pwd = input('Please input password:')  # 输入用户密码
    27             f_locked.seek(0)        #"a+"模式打开后,文件位置位于末尾,要遍历文件内容,需要将指针移至文件起始位置
    28             for locked_user in f_locked:        #将被锁定用户名单写入列表
    29                 l.append(locked_user.strip())
    30             if name in l:
    31                 print('This user has been locked!')     #如果当前欲登陆用户在被锁定列表中,提示并重新输入登陆信息
    32             else:
    33                 user_input.append(name)     #将当前输入的用户名加入到列表
    34                 f_account.seek(0)  #循环前将文件位置移至起始位置
    35                 for line in f_account:      #遍历用户登陆数据文件
    36                     s = eval(line)          #将该文件的内容转换为字典格式
    37                     if name == s['name'] and pwd == s['password']:     #判断用户名和密码是否正确
    38                         print('Authenticate successful')        #用户名和密码匹配,认证成功,结束循环,并将f=1返回
    39                         f = 1
    40                         flag = False
    41                         break
    42                 if f == 1:      #用户名和密码不匹配,提示用户输入错误
    43                     continue
    44                 print('Wrong name or password!')
    45                 count += 1      #错误次数加1,当count等于3时,结束循环
    46         if len(user_input) == 3:        #如果该列表长度等于3,说明用户3次登陆均失败
    47             if user_input[0] == user_input[1] == user_input[2]:     #判断3次登陆是否时同一用户名
    48                 f_account.seek(0)       #重置文件位置为起始位置
    49                 l = []      #新建空列表,存放用户登陆文件中的用户名信息
    50                 for line in f_account:      #遍历用户登陆文件
    51                     s = eval(line)           #将行内容转换为字典格式
    52                     l.append(s['name'])     #将用户名加入到列表
    53                 if user_input[0] in l:      #判断登陆失败的用户名是否在上述列表中
    54                     print('This user has been locked!')     #提示用户将锁定该登陆名
    55                     f_locked.write(user_input[0] + '
    ')    #将该登录名加入锁定文件
    56                     f_locked.flush()        #实时刷新文件
    57     return f
    58 
    59 
    60 def auth(source):
    61     """用户登陆认证程序的装饰器"""
    62     def auth_main(func):
    63         def wrapper(*args, **kwargs):
    64             if source == 'file':        #判断认证来源类型是否为‘file’
    65                 if (identiy() == 1):        #调用用户登陆认证程序
    66                     res = func(*args, **kwargs)     #运行被装饰的程序
    67                     return res
    68             elif source == 'ldap':      #另外一种认证来源类型
    69                 def wrapper_2(*args, **kwargs):
    70                     print('Nothing here!')
    71                     pass
    72         return wrapper
    73     return auth_main
    74 
    75 
    76 @auth(source = 'file')      #将用户登陆认证功能装饰程序‘func_1’
    77 @run_timer                  #将程序计时功能装饰程序‘func_1’
    78 def func_1():               #被装饰程序
    79     time.sleep(1)
    80     print('This is function_1')
    81 
    82 
    83 func_1()        #调用被装饰程序
    计时+登陆认证 装饰器

    验证过程:

    1. account文件:存放用户登陆数据

    2. locked_list文件:存放被锁定用户名

      当前为空

    3. 尝试登陆正确的用户

    4. 尝试不同用户登陆失败

    5. 尝试同一用户登陆失败

    6. 尝试同一用户登陆失败,但该用户本身不存在

     

    参考资料:

    1. http://www.cnblogs.com/feixuelove1009/p/5541632.html

    2. http://www.cnblogs.com/wupeiqi/articles/4943406.html

     

     

  • 相关阅读:
    乱谈B2C系统算是今年的总结吧
    浅谈领域模型驱动中表的设计方法
    作业调度小软件
    使用Mutex实现会话状态下单实例运行和系统范围内单实例运行
    几种特殊的类型设计。
    XCommunity权限控制和配置体系
    某个最近不知道为啥很火的小题目的LINQ实现
    C#关于参数为null(空值)的方法调用,重载顺序选择彻底研究
    好吧,不得不说说这篇在首页恶心人的文章
    “九种不够面向对象的对象“的在实际项目中的合理运用
  • 原文地址:https://www.cnblogs.com/OldJack/p/6691436.html
Copyright © 2011-2022 走看看