Python中的装饰器

装饰器

  • 概述

    • 装饰器是修改其他函数的功能的函数

  • 菜鸟教程讲述链接

  • 示例

    • 不使用@符号,直接调用装饰器函数

    def wrapper(func):
       def doSomethingBeforeHi():
           print("I am doing some boring work before executing hi()")
           print(func())
       return doSomethingBeforeHi

    def hi():
       return "hi yasoob!"

    a = wrapper(hi)
    a()
    • 使用@符号代替调用装饰器的过程

      • 优点:代码其他部分可依然调用原函数名称,实现调用装饰器的过程

      • 问题:运行如下代码会存在一个问题,Ouput输出不是原函数的名字和注释文档

    def wrapper(func):
       a = 1
       def hello():
           nonlocal a
           print('hello')
           func()
           a += 1
           print(a)
           print('good bye')
       print(a+5)
       return hello

    @wrapper
    def tmp():
       print('aaa')

    if __name__ == '__main__':
       tmp()
       print(tmp)
    6
    hello
    aaa
    2
    good bye
    <function wrapper.<locals>.hello at 0x000001C6FF5EDE58>
    • 解决办法:使用functools.wraps函数,导入functools中的wraps。修改装饰器函数的代码如下 @wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性

    from functools import wraps

    def wrapper(func):
       a = 1
       @wraps(func)
       def hello():
           nonlocal a
           print('hello')
           func()
           a += 1
           print(a)
           print('good bye')
       print(a+5)
       return hello
    <function tmp at 0x000002757F39DE58>
  • 装饰器使用场景

    • 授权(Authorization):装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:

    from functools import wraps

    def requires_auth(f):
       @wraps(f)
       def decorated(*args, **kwargs):
           auth = request.authorization
           if not auth or not check_auth(auth.username, auth.password):
               authenticate()
           return f(*args, **kwargs)
       return decorated
    • 日志(Logging):日志是装饰器运用的另一个亮点。这是个例子:

    from functools import wraps

    def logit(func):
       @wraps(func)
       def with_logging(*args, **kwargs):
           print(func.__name__ + " was called")
           return func(*args, **kwargs)
       return with_logging

    @logit
    def addition_func(x):
      """Do some math."""
      return x + x


    result = addition_func(4)
    # Output: addition_func was called
  • 带参数的装饰器

    • 我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件

    from functools import wraps

    def logit(logfile='out.log'):
       def logging_decorator(func):
           @wraps(func)
           def wrapped_function(*args, **kwargs):
               log_string = func.__name__ + " was called"
               print(log_string)
               # 打开logfile,并写入内容
               with open(logfile, 'a') as opened_file:
                   # 现在将日志打到指定的logfile
                   opened_file.write(log_string + '\n')
               return func(*args, **kwargs)
           return wrapped_function
       return logging_decorator

    @logit()
    def myfunc1():
       pass

    myfunc1()
    # Output: myfunc1 was called
    # 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串

    @logit(logfile='func2.log')
    def myfunc2():
       pass

    myfunc2()
    # Output: myfunc2 was called
    # 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
  • 装饰器类

    • 将上述日志功能,改造为类的方式

    • 优点:比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法

    from functools import wraps

    class logit(object):
       def __init__(self, logfile='out.log'):
           self.logfile = logfile

       def __call__(self, func):
           @wraps(func)
           def wrapped_function(*args, **kwargs):
               log_string = func.__name__ + " was called"
               print(log_string)
               # 打开logfile并写入
               with open(self.logfile, 'a') as opened_file:
                   # 现在将日志打到指定的文件
                   opened_file.write(log_string + '\n')
               # 现在,发送一个通知
               self.notify()
               return func(*args, **kwargs)
           return wrapped_function

       def notify(self):
           # logit只打日志,不做别的
           pass
    @logit()
    def myfunc1():
       pass
    • 还可以给logit 创建子类,来添加 email 的功能

    class email_logit(logit):
       '''
      一个logit的实现版本,可以在函数调用时发送email给管理员
      '''
       def __init__(self, email='admin@myproject.com', *args, **kwargs):
           self.email = email
           super(email_logit, self).__init__(*args, **kwargs)

       def notify(self):
           # 发送一封email到self.email
           # 这里就不做实现了
           pass
  • 装饰器执行顺序

    • 一个函数还可以同时定义多个装饰器,执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器

    @a
    @b
    @c
    def f ():
       pass
    等效于:
    f = a(b(c(f)))
  • 总结

    • 举例理解

    每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,让它变得更厚更长,这样一来,它不仅有遮羞功能,还能提供保暖,不过有个问题,这个内裤被我们改造成了长裤后,虽然还有遮羞功能,但本质上它不再是一条真正的内裤了。于是聪明的人们发明长裤,在不影响内裤的前提下,直接把长裤套在了内裤外面,这样内裤还是内裤,有了长裤后宝宝再也不冷了。装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。

posted @ 2021-05-20 18:31  王滚滚啊  阅读(68)  评论(0)    收藏  举报