首页 > 技术文章 > Python基础学习笔记(14)装饰器

raygor 2020-07-12 16:22 原文

Python基础学习(14)装饰器

一、今日内容大纲

  • 装饰器 decorator
  • 装饰器的应用

二、装饰器

  1. 开放封闭原则

    开放:对代码的拓展开放

    封闭:对源码的修改封闭

  2. 装饰器 decorator

    完全遵循开放封闭原则,是一个函数,本质上属于闭包 closure 的应用;在不改变原函数代码及调用方式的前提下,为其增加新的功能。

    现在,就一个测试函数执行效率的问题,我们对其进行讨论:

    • version 1: 写一些代码测试index()函数的执行效率。

      import time
      def index():
          time.sleep(2)  # 模拟网络延迟或代码效率
          print('welcome cnblogs.com')
      
      # print(time.time())
      # 格林威治时间 Greenwich Mean Time 返回1970纪元后的浮点秒数
      start_time = time.time()
      index()
      end_time = time.time()
      print(end_time - start_time)
      # summary: 如果要测试其他函数的代码效率,必须重新编写代码,效率低下。
      
    • version 2: 利用函数解决代码重复使用的问题。

      import time
      def index1():
          time.sleep(2)  # 模拟网络延迟或代码效率
          print('welcome cnblogs.com')
      
      def index2():
          time.sleep(3)  # 模拟网络延迟或代码效率
          print('enter the diary page')
      
      def timer(func):
          start_time = time.time()
          func()
          end_time = time.time()
          print(end_time - start_time)
      
      timer(index1)  # 修改了调用方式
      timer(index2)
      # summary:原来index函数源码没有变化,给原函数添加了一个测试执行效率的功能;但是不满足开放封闭原则,因为版本二修改了调用方式。
      
    • version 3: 着手调用方式问题,使装饰后的函数调用方式和原函数调用更加相似。

      import time
      def index1():
          time.sleep(2)  # 模拟网络延迟或代码效率
          print('welcome cnblogs.com')
      
      def index2():
          time.sleep(3)  # 模拟网络延迟或代码效率
          print('enter the diary page')
      
      def timer(func):
          def inner():
              start_time = time.time()
              func()
              end_time = time.time()
              print(end_time - start_time)
      
          return inner
      
      index1 = timer(index1)  # 多余项目
      index2 = timer(index2)  # 多余项目
      index1()
      index2()
      # summary:这就是最基础的装饰器,但是多了一项赋值运算,还需要优化
      
    • version 4: Python做了一个优化,提出了语法糖(Syntactic sugar)的概念,用来替代那项多余的赋值运算。

      import time
      # timer装饰器
      def timer(func):
          def inner():
              start_time = time.time()
              func()
              end_time = time.time()
              print(end_time - start_time)
      
          return inner
      
      @timer  # 等价于index1 = timer(index1) 读了两行
      def index1():
          time.sleep(2)  # 模拟网络延迟或代码效率
          print('welcome cnblogs.com')
      
      @timer  # 等价于index2 = timer(index2) 读了两行
      def index2():
          time.sleep(3)  # 模拟网络延迟或代码效率
          print('enter the diary page')
      
      index1()
      index2()
      # summary:在无传参的情况下,基本实现了开放封闭原则,但是如果被装饰函数有参数传输,又会出现问题。
      
    • version 5: 被装饰函数拥有返回值时的情况。

      import time
      # timer装饰器
      def timer(func):
          def inner():
              start_time = time.time()
              r = func()  # 实际的返回值在这里
              end_time = time.time()
              print(end_time - start_time)
              return r  # 将index1()返回值通过inner()返回
      
          return inner
      
      @timer  # 等价于index1 = timer(index1) 读了两行
      def index1():
          time.sleep(2)  # 模拟网络延迟或代码效率
          print('welcome cnblogs.com')
          return 666
      
      @timer  # 等价于index2 = timer(index2) 读了两行
      def index2():
          time.sleep(3)  # 模拟网络延迟或代码效率
          print('enter the diary page')
          return 777
      
      ret1 = index1()  # inner()的返回值要变成index1()的返回值
      ret2 = index2()
      print(ret1, ret2)
      # summary:通过inner()将index()返回值返回
      
    • version 6: 被装饰函数有参数传入时的情况。

      import time
      # 标准timer装饰器*******************
      def timer(func):
          def inner(*args, **kwargs):  # *代表聚合
              start_time = time.time()
              r = func(*args, **kwargs)  # *代表打散,相当于func(*(),**{})
              end_time = time.time()
              print(end_time - start_time)
              return r  # 将index1()返回值通过inner()返回
      
          return inner
      
      @timer  # 等价于index1 = timer(index1) 读了两行
      def index1(name):
          time.sleep(2)  # 模拟网络延迟或代码效率
          print(f"welcome 'cnblogs.com', {name}.")
          return 666
      
      @timer  # 等价于index2 = timer(index2) 读了两行
      def index2(name, title):
          time.sleep(3)  # 模拟网络延迟或代码效率
          print(f'{title}{name} enter the diary page')
          return 777
      
      ret1 = index1('太白')  # inner()的返回值要变成index1()的返回值
      ret2 = index2('纳钦', 'Dr.')
      print(ret1, ret2)
      
  3. 装饰器的标准形式

    # 标准版的装饰器
    def wrapper(f):
        def inner(*args, **kwargs):
            """添加额外功能:f()之前的装饰"""
            ret = f(*args, **kwargs)
            """添加额外功能:f()之后的装饰"""
            return ret
        return inner
    

三、装饰器的应用:以博客园的登录为例

# 装饰器的应用:登录认证
# 这周的周末作业:模拟博客园登录,需要利用装饰器的认证功能
login_status = dict(
    status=False,
    username=None
)
def get_users_info():
    users_info = {}
    with open(r'02 装饰器的应用\users_info', encoding='utf-8') as file_handler:
        for line in file_handler:
            li = line.split('|')
            users_info.setdefault(li[0].strip(), li[1].strip())
    return users_info

def login():
    users_info = get_users_info()
    # print(users_info)
    for i in range(3):
        username = input('username:')
        password = input('password:')
        if username in users_info and users_info[username] == password:
            login_status['status'] = True
            login_status['username'] = username
            return True
        else:
            print('The account password you entered is not correct.')
    return False


def auth(func):
    def inner():
        if not login_status['status']:
            login()
        if login_status['status']:
            func()
        return
    return inner

@auth
def article():
    print('欢迎访问文章页面')

@auth
def comment():
    print('欢迎访问评论页面')

@auth
def dairy():
    print('欢迎访问评论页面')

推荐阅读