zoukankan      html  css  js  c++  java
  • python装饰器

    一、装饰器概述

    python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
    简单的说装饰器就是一个用来返回函数的函数。
    
    它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,
    有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
    
    概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
    
    
    认识装饰器之前,需要明确的事情:
    # 装饰器在不改变函数内部的基础上,可以统一添加某些功能,让函数在执行之前或之后做一些操作
    # 定义完函数未调用,函数内部不执行
    # 函数名(不加括号),代指函数整体;加括号:执行函数
    # 函数名可以当作参数传递
    # 只要函数用上装饰器,那么函数就会被重新定义为装饰器的内层函数

    二、为什么要使用装饰器

    1、来个例子说明

    首先有个函数:

      1 def foo():
      2     print('i am foo')

    现在希望记录函数执行的日志,可以写成下面这样:

      1 def foo():
      2   print('i am foo')
      3   print("foo is running")

    那么如果有100个函数都要增加记录日志的需求呢?

    这样我们可以写一个专门的函数,让它干记录日志的活:

    # 专门记录日志的函数
    def use_logging(func): 
        print("%s is running" % func.__name__)
        func()
    
    # 原函数
    def bar():
        print('i am bar')
    
    # 使用,把原函数当作参数传给use_logging
    use_logging(bar)
    
    # 结果如下
    bar is running
    i am bar

    三、装饰器入门

    1、装饰器语法糖

    # python提供了@符号作为装饰器的语法糖,使我们更方便的应用装饰函数。但使用语法糖要求装饰函数必须return一个函数对象。
    
    # 只要函数用上装饰器,那么会发生以下事情:
        # 原函数就会被重新定义为装饰器的内层函数
        # 哪个函数调用了装饰器,就会将哪个函数的函数名当作参数传给装饰器函数
        # 将装饰器函数的返回值,重新赋值给原函数
    
    
    def use_logging(func):  # 这里的func就是bar
        # 这里的inner函数,就是bar函数
        def inner():
            print("{} is running!".format(func.__name__))  # 新增加的功能
            return func()  # func就是bar,因为装饰器不能改变原函数,所以还要返回一个老的bar的值
        return inner   # 返回包装过的函数,这里的inner也就是bar,这里先没有带(),在最后执行bar时(也就是执行inner)才带上()
    
    
    @use_logging  #含义:use_logging(bar)
    def bar():     # 函数bar调用了装饰器,会将bar当参数传给use_logging
        print("i am bar")
    
    bar()  

    2、带参数的函数使用装饰器

    def use_logging(func):
        def inner(a, b):
            print("{} is running!".format(func.__name__))
            return func(a, b)
        return inner
    
    @use_logging
    def bar(a, b):
        print("The sum of a and b is {}".format(a+b))
    
    bar(1, 2)
    
    
    # 我们装饰的函数可能参数的个数和类型都不一样,每一次我们都需要对装饰器做修改吗?
    # 这样做当然是不科学的,因此我们使用python的变长参数*args和**kwargs来解决我们的参数问题。

    如下:

    # 这样就可以适应带参数的函数了
    def use_logging(func):
        def inner(*args, **kwargs):
            print("{} is running!".format(func.__name__))
            return func(*args, **kwargs)
        return inner
    
    @use_logging
    def bar(a, b):
        print("The sum of a and b is {}".format(a+b))
    
    bar(1, 2)

    上面是带参数的函数和不带参数的装饰器,下面看一下带参数的装饰器;

    3、带参数的装饰器

    # 某些情况我们需要让装饰器带上参数,那就需要编写一个返回一个装饰器的高阶函数,比较复杂
    
    def use_logging(level):
        def inner1(func):  # 这里的func是bar
            def inner2(*args, **kwargs):
                if level == "warn":
                    print("{} is running!".format(func.__name__))
                return func(*args, **kwargs)
            return inner2
        return inner1
    
    @use_logging(level="warn")
    def bar(a, b):
        print("The sum of a and b is {}".format(a+b))
    
    bar(1, 2)

    4、functools.wraps

    使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

    def use_logging(func):
        def inner(*args, **kwargs):
            print("%s is running" % func.__name__)
            func(*args, **kwargs)
        return inner
    
    
    @use_logging
    def bar():
        print('i am bar')
        print(bar.__name__)  # 结果成了:inner
    
    bar()
    
    # 结果:
    bar is running
    i am bar
    inner
    
    #函数名变为inner而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入functools.wraps解决这个问题。

    使用functools.wraps:

    from functools import wraps   # 导包
    def use_logging(func):
        @wraps(func)
        def inner(*args, **kwargs):
            print("%s is running" % func.__name__)
            func(*args, **kwargs)
        return inner
    
    
    @use_logging
    def bar():
        print('i am bar')
        print(bar.__name__)  # OK了,结果是bar
    
    bar()
    
    # 结果
    bar is running
    i am bar
    bar

    5、双层装饰器

    # 双层装饰器,执行顺序是从上往下执行(outer1-->outer2); 调用(解释)顺序是先outer2再outer1
    # 返回顺序也是outer1-->outer2
    
    def outer1(func):
        def inner(*arg, **kwargs):
            print("outer1")
            return func(*arg, **kwargs)
        return inner
    
    def outer2(func):
        def inner(*arg, **kwargs):
            print("outer2")
            return func(*arg, **kwargs)
        return inner
    
    @outer1
    @outer2
    def a1():
        print("a1")
    
    a1()
    
    # 结果
    outer1
    outer2
    a1

    四、类装饰器

    使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展.

    1、类装饰器使用

    from functools import wraps
    class loging(object):
        def __init__(self, level="warn"):
            self.level = level
        def __call__(self, func):
            @wraps(func)
            def inner(*args, **kwargs):
                if self.level == "warn":
                    self.notify(func)
                return func(*args, **kwargs)
            return inner
        def notify(self, func):  # 打印日志的函数
            print("{} is running".format(func.__name__))
    
    
    @loging(level="warn")  # 执行__call__方法
    def bar(a, b):
        print("The sum of a and b is {}".format(a+b))
    
    bar(1, 2)
  • 相关阅读:
    动态规划5-多重背包
    动态规划4-完全背包
    利用dwebsocket在Django中使用Websocket
    Java学习笔记之:Spring MVC 环境搭建
    Struts2 国际化
    Java学习笔记之:Struts2.0 环境搭建
    LoadRunner:VuGen开发脚本步骤(二)
    LoadRunner:VuGen开发脚本步骤(一)
    Java学习笔记之:Java Servlet 过滤器配置
    Java学习笔记之:Java Servlet环境配置
  • 原文地址:https://www.cnblogs.com/weiyiming007/p/12402862.html
Copyright © 2011-2022 走看看