python学习笔记 | 装饰器

前言

1. 如何理解Python装饰器?

装饰器本质上是一个「Python函数」,它可以让其他函数在不需要做任何代码变动的前提下增加额外的扩展功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:

  1. 插入日志
  2. 性能测试
  3. 事务处理
  4. 缓存
  5. 权限校验

2. 为什么python要引入装饰器?

image-20210328212130102

🥴 🥴 🥴

3. 参考资料

装饰器技术的理解

  1. 函数即变量 - 一个带有括号的变量

    定义一个函数,调用时如果不加括号则返回其「内存地址」,加括号则直接调用这个函数。

    def fun():
        print('hello world!')
    fun()
    print(fun)
    
    '''运行结果
    hello world!
    <function fun at 0x111522940>
    '''
    
  2. 高阶函数 - 把一个函数作为「实参」传入另个函数

    python允许一个函数接收另一个函数作为参数,这种函数就称之为高阶函数。带来的一个直接好处就是,在不修改被装饰函数源代码的情况下为其添加功能。

    def accept(func):
        print("this is accept()")
    	   func()
    def deliver():
        print("this is deliver()")
    accept(deliver)
    
    '''运行结果
    this is accept()
    this is deliver()
    ''' 
    

    python中5个常用的内置高阶函数

  3. 嵌套函数 - 在一个函数内声明一个函数

    def fun1():
        x=1
        def fun2():
            x=2
            print(x)
        fun2()
    fun1()
    
    '''运行结果
    2
    '''
    

    除了声明函数,也可使用return关键字返回这个函数对象。

内置装饰器

  • @wraps(func) - 确保被装饰函数的属性(如__name____doc__)不被改变

    from functools import wraps
    def decorator(func):
        @wraps(func) # 
        def wrapper(*args,**kwargs):
            return func(*args,**kwargs)
        return wrapper
    
    @decorator
    def fruit():
      return __name__+"我爱吃香蕉。"
    
    print(fruit.__name__) # fruit
    
  • @property - 访问被装饰函数的函数名时,会返回被装饰函数的执行代码及返回值

    class NB:
        @property # 
        def name(self):
          return "Billie"
    
    NB().name # Billie
    
  • @staticmethod - 声明某个类中的函数为静态方法,可以实现非实例化调用该方法

    class ExcelOperator:
        @staticmethod # 
        def test(): # 注意,这里没有self参数
            return 6
    
    ExcelOperator().test() # 6
    ExcelOperator.test() # 也是6
    

    property 和 staticmethod 功能很类似,一个是Class().func一个是Class.func()(强行和谐 👌

  • @classmethod - 类方法

    class Xin:
      motto = '你就好啦'
    
      @classmethod
      def jie(cls):
        print(cls.motto)
    

装饰器实例

1. 最基本的装饰器 - 高阶函数+嵌套函数

def logger(func):
    def wrapper():
        print(f"开始执行函数 - {func.__name__}")
        func()
    return wrapper
def fun():
    print('hello world!')
f = logger(fun)
f()

logger函数就是装饰器,它把真正执行业务方法的func包裹在函数里面,看起来像fun被logger装饰了,在这个例子中,函数进入和退出时,被称为一个横切面(Aspect),这种编程方式被称为「面向切面的编程(Aspect-Oriented programming)」,执行结果如下

image-20210404000722974

使用return返回函数对象时,不带括号则返回这个函数的内存地址,带上括号则直接运行,如下:

image-20210404000814834

2. 带语法糖的装饰器

@ 符号是装饰器的语法糖,在定义函数的时候使用,只需在待执行装饰器的函数上方加上「@装饰器名」即可。

def logger(func):
  def wrapper():
    print(f"正在执行函数 - {func.__name__}")
    func()
    print(f"结束运行函数 - {func.__name__}")
	return wrapper

@logger
def fun():
  print('hello world!')

fun()

使用了语法糖的方法,就可以直接调用fun(),如果还有其他需要执行装饰器的执行函数,也可直接用这样的方式套用,而不用重复修改函数或者增加新的封装。这样一来,就提高了程序的复用性和可读性。

image-20210404001032763

3. 可传参的装饰器

我们先来唠一唠参数吧,python函数中的参数极具魅力,各种参数的交替使用可充分提高开发效率。

根据使用位置的不同,可以分为形参和实参,形参就是形式上的参数,在定义函数时传入的参数我们统称为形式参数;实参就是实际意义上的参数,在调用这个函数时传入的参数统称为实参。

根据传入形式的不同,可以分为位置参数、默认参数、关键字参数、可变参数。这里我们着重关注可变参数的使用,可变参数又分为可变位置参数*args(argument)和可变关键字参数**kwargs(keyword argument)。定义函数时,如不确定调用的时候会传递多少个参数(不传参也可以)。此时,可通过*args**kwargs,来包裹位置参数或者包裹关键字参数,来进行参数传递,会显得非常优雅 🦷

image-20210404112258770

更多请参考 - python的位置参数、默认参数、关键字参数、可变参数区别

为了给予装饰器更大的灵活性,例如带参数的装饰器,我们也可以给装饰器进行传参的操作。一般而言,我们编写的装饰器需要应用于多个函数,而被应用函数传入的参数都不固定,为了使得我们的装饰器能够通用于所有的函数,编写装饰器时可传入可变参数,即*args**kwargs

def logger(func):
    def wrapper(*args,**kwargs): #
        print(f"正在执行函数 - {func.__name__}")
        func(*args,**kwargs) #
        print(f"结束运行函数 - {func.__name__}")
	  return wrapper

@logger
def fun(name,lang):
    print(name)
    print(lang)

fun('Billie',lang='python')

image-20210404113530144

4. 带参数的装饰器

使用时给装饰器函数进行传参。

def logger(flag=True):
    def wrapper(func): #
        def inner(*args, **kwargs): #
            if flag:
                print(f"正在执行函数 - {func.__name__}")
                func(*args, **kwargs) #
                print(f"结束运行函数 - {func.__name__}")
        return inner
    return wrapper
@logger(False)
def fun():
    print('hello world!')
fun()

image-20210406113440255

5. 类装饰器

class decorator:
  def __init__(self):
  def __call__(self):
  def __set__(self):

6. 装饰器示例

# 异常处理装饰器
def log(flag):
    def wrapper(func):
        @wraps(func)
        def inner(*args,**kwargs):
            if flag:
                print(f'正在执行 - {func.__name__}')
                logging.info(f'正在执行 - {func.__name__}')
            try:
                return func(*args, **kwargs)
            except Exception as e:
                logging.error('程序报错提示:', repr(e) + '\n' + traceback.format_exc())
        return inner
    return wrapper
class StopRetryError(Exception):
    pass
  
# 重试
def retry(times=5):
    def decorator(func):
        @wraps(func)
        def inner_decorator(*args, **kwargs):
            error = None
            error_msg = None
            for _ in range(times):
                try:
                    if error_msg:
                        rpa_logger.error(f'程序运行出错, 错误信息 {error_msg}, 进入重试')
                    return func(*args, **kwargs)
                except StopRetryError as e:
                    raise e
                except Exception as e:
                    error = e
                    error_msg = str(e)
            else:
                raise error

        return inner_decorator

    return decorator

使用频率比较高的装饰器

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logging.error(repr(e) + '\n' + traceback.format_exc())
    return wrapper
def makedirs(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except FileNotFoundError as e:
            file_path = re.search(": '(.*?)'", str(e)).group(1)
            file_dir = os.path.split(file_path)[0]
            os.makedirs(file_dir) #
            logging.info(f"makedirs: {file_dir}")
        except Exception as e:
            logging.error(repr(e) + '\n' + traceback.format_exc())
    return wrapper

后记

更多 - python学习笔记 | 一次使用装饰器时引发的思考


2162 字

Powered By Valine
v1.5.2