Python 装饰器 Decorator

初步认识

给已有函数增加额外功能的函数,但不改变原有函数。

装饰器本质上也是一种闭包函数,装饰器函数的参数仅有一个且是函数类型

举个例子,你有一个函数logic,作用是查询信息等逻辑。现在需求是:要在查询信息之前做身份认证,怎么办?

通常你可以在logic函数中的查询信息之前增加身份认证的逻辑,从而达到要求,如下:


# 代码块一

# 原函数
def logic():
    print('正在查询信息...')

# 增加认证逻辑后
def logic():
    print('正在认证...')                # 函数体就被修改了
    print('正在查询信息...')

可以发现,这样改变了这个函数本身。而使用装饰器不改变原有函数。

先初步看看装饰器的写法,如下:


# 代码块二:原始装饰器的写法

# 原函数
def logic():
    print('正在查询信息...')

# 装饰器函数
def decorator(func):
    def inner():
        print('正在认证...')
        func()                     # 原 logic 函数在此被调用
    return inner

# 调用
new_logic = decorator(logic)
new_logic()

可以初步感觉得到,装饰器函数就是对原有函数进行"外部包装"

在python中,有专门的语法糖描述上述写法,如下:


# 代码块三:语法糖的写法

# 装饰器函数
def decorator(func):
    def inner():
        authenticate()
        func()
    return inner

# 语法糖
@decorator
def logic():
    print('正在查询信息...')

# 调用
logic()            # 由于语法糖中 decorator 的装饰作用,此 logic 函数具备了认证、查询的功能

装饰器特点

基于上述的简单认识,装饰器有如下有特点:

  1. 不修改已有函数的源代码

  1. 不修改已有函数的调用方式

  1. 给已有函数增加额外的功能

装饰器语法糖 @ 符号的分析

回顾上述代码块二,定义了装饰器函数decorator后,将logic函数作为装饰器函数decorator的参数传入,赋给new_logic变量,然后执行new_logic()。

而 @ 符号的左右正是这一过程的描述,只不过 @ 符号赋给的变量不是重新命个名(如上述赋给变量new_logic),而是赋给原函数这个变量,即 logic = decorator(logic),然后执行logic()


def logic():
    print('正在查询信息...')

def decorator(func):
    def inner():
        print('正在认证...')
        func()                    
    return inner

logic = decorator(logic)      # 赋给原logic变量
logic()                       # 此时调用的logic不再是原logic,而是被装饰后的logic

-------------------------------上下对比---------------------------------

def decorator(func):
    def inner():
        print('正在认证...')
        func()                   
    return inner

@decorator
def logic():
    print('正在查询信息...')

# 调用
logic() 

@符号的本质逻辑就是,将被装饰的函数func作为装饰器decorator的参数传入,装饰器返回的inner(实质是一个闭包)赋给原函数func变量,当调用func时,实质是调用已经被装饰了的func

装饰器的副作用

细心的你会发现,装饰后赋给原函数变量,意味着原函数将无法被调用。因为调用的函数是已经被装饰了的函数。

PS:一般情况下,不再需要原函数,也就不需要调用原函数了,那这个副作用可以忽略,因此下一句话初学者可暂时不用理解

准确的说,原函数将无法被直接调用。装饰时会使原函数的部分属性被改变,如__name__,所以调用到的是装饰后的函数,若想调用原函数,可使用from functools import wraps 的wraps方法,这里不做详述

装饰过程 与 执行过程

装饰过程指的是,装饰器函数对原函数的作用过程。

执行过程指的是,对已被装饰了的函数的调用过程

python程序在启动时,将py源码编译成字节码,然后解释执行。而装饰过程就发生在程序运行之前!也就是说,运行代码之前,装饰器函数对原函数的作用过程已执行完毕,即@符号代表的装饰过程已经被解释了。然后代码运行时,才真正调用被装饰的函数。

从多个装饰器作用的角度看 装饰过程 与 执行过程

以下模拟一个苹果,先被装入一个塑料袋,然后一起被装入一个箱子


def add_plastic(func):
    print(' --- add_plastic 装饰器执行了 --- ')

    def inner1():
        print(' --- 调用 add_plastic 装饰器的inner --- ')
        result = '塑料袋' + func() + '塑料袋'
        return result

    return inner1


def add_box(func):
    print(' --- add_box 装饰器执行了 --- ')

    def inner2():
        print(' --- 调用 add_box 装饰器的inner --- ')
        result = '箱子' + func() + '箱子'
        return result

    return inner2

@add_box
@add_plastic
def content():
    print(' --- 调用 content --- ')
    return '####我是一个苹果####'

result = content()
print(result)

# 输出结果:
 --- add_plastic 装饰器执行了 --- 
 --- add_box 装饰器执行了 --- 
 --- 调用 add_box 装饰器的inner --- 
 --- 调用 add_plastic 装饰器的inner --- 
 --- 调用 content --- 
箱子塑料袋####我是一个苹果####塑料袋箱子

分析得出,

多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。

实际函数调用时,装饰器内部函数inner执行的过程是:最外层装饰器的内部函数inner先执行

函数装饰器的类型 及 通用装饰器

基本原则:装饰器内部函数inner的参数类型以及是否有返回值与被装饰的函数保持一致!!!

根据不同的函数,装饰器的写法如下


# -----------------装饰带有参数的函数-------------------
def decorator(func):
    def inner(a, b):
        print('计算进行中...')
        func(a, b)
    return inner

@decorator
def add(num1, num2):
    result = num1 + num2
    print('结果为:', result)

add(1, 3)

# --------------装饰带有返回值的函数--------------------
def decorator(func):
    def inner(a, b):
        print('计算进行中...')
        result = func(a, b)
        return result
    return inner

@decorator
def add(num1, num2):
    result = num1 + num2
    return result

result = add(2, 5)
print(result)

于是,有了通用装饰器写法,如下:


# 通用装饰器,即不管原函数啥样,装饰器函数都可以这么写
def decorator(func):
    print('start decorate, you can do some decorate-related operation here ------')

    def inner(*args, **kwargs):
        print('you can do some other operation here before origin func ------')
        return func(*args, **kwargs)   # 函数没有返回值时,接收的为None
    return inner

@decorator
def add(*args, **kwargs): 
    result = 0

    # 若是位置参数则args接收
    for value in args:
        result += value
    # 若是关键字参数则kwargs接收
    for value in kwargs.values():
        result += value

    return result

result = add(1, 5)
print('result', result)

带参数的装饰器

带有参数的装饰器:本质是将装饰器封装在一个带参有返回值的函数里,这个返回值返回该装饰器。即装饰器外面再包装一个带参函数。

注意:不是装饰器函数本身的参数中增添其他参数,因为装饰器函数的参数有且仅有一个,且是函数类型

写法如下:


def return_decorator(flag):
    # 对装饰器外包一个带参且有返回值函数,即可实现对装饰器的传参(这里的参数是decorator本身需要的,而不是inner需要的)

    # def decorator(func, flag):                   # 不合法,flag无法接收
    def decorator(func):                           # 装饰器函数,有且仅有一个函数类型的参数
        def inner(a, b):
            if flag == '+':
                print('加法计算进行中...')
            elif flag == '-':
                print('减法计算进行中...')
            func(a, b)
        return inner

    # 调用外包函数时,返回该装饰器,此时拿到了参数flag
    return decorator

@return_decorator('+')      # 相当于decorator = return_decorator('+')   @decorator ==> add = decorator(add)
def add(a, b):
    result = a + b
    print(result)

@return_decorator('-')
def sub(a, b):
    result = a - b
    print(result)

add(1, 5)
sub(4, 2)

类装饰器 与 类实例装饰器

  1. 类装饰器

通过类来装饰函数func,相当于无参装饰器


class ClassDecorator(object):
    def __init__(self, func):                            # 相当于装饰器函数
        print('类的初始化过程就是装饰的过程, you can do some decorate-related operation here ------')
        self.func = func

    def __call__(self, *args, **kwargs):                  # 被装饰函数 func 有参数时被 args/kwargs接收
        print('被装饰后,新函数(实例)被调用的执行 ------')
        print('函数实参-----', args, kwargs)

        print('you can add some other operation before origin func ------')
        res = self.func(*args, **kwargs)
        print('you can add some other operation after origin func ------')

        return res

@ClassDecorator           # 相当于 show = ClassDecorator(show), 此时的 show 就是实例对象
def show(f1, f2):
    add_sum = f1 + f2
    print('----func show is executing------', add_sum)
    return add_sum

# 装饰后的 show 即是一个ClassDecorator对象
print(show)                                            # 输出 show,为 ClassDecorator 对象,此时并未执行 __call__ 方法
add_sum = show(10, f2=22)                              # show (实例)的调用即是 __call__ 的执行,若 __call__ 方法有返回值,则实例的调用有返回值
print(add_sum)

过程分析:show = ClassDecorator(show) 装饰过程就是类的初始化

被装饰函数将函数名func作为参数传给类,对类初始化返回一个实例对象,

并将这个实例对象赋给func(或者说将这个实例对象重命名为func)

以后对func的调用,就是对该类实例对象的调用,调用时会执行__call__方法

PS:函数本身也是类。函数之所以能够被调用,是因为函数类内部实现了__call__方法

  1. 类实例装饰器

通过实例来装饰函数func,可以在类初始化时进行传参,即带参装饰器。

如web框架flask的路由映射: @route('/index', methods=[])


class InstanceDecorator:
    def __init__(self, d1, d2):                        # 类初始化(即装饰器初始化)时传参,即带参装饰器
        self.d1 = d1
        self.d2 = d2
        print('initialize decorator, you can do some operation related to decorator itself here')
        self.extra_init()

    def extra_init(self):
        """
        这里可以根据参数d1, d2对装饰器进行初始化配置
        """
        pass

    def __call__(self, func):                                       # 相当于装饰器函数,被装饰函数作为参数func传入
        print('start decorate, you can do some decorate-related operation here ------')

        def wrapper(*args, **kwargs):                               # 被装饰函数func有参数时被args/kwargs接收
            print('函数实参-----', args, kwargs)

            print('you can add some other operation before origin func ------')
            res = func(*args, **kwargs)
            print('you can add some other operation after origin func ------')

            return res

        return wrapper                                                # 返回一个闭包wrapper(保存了原func函数),覆盖到原func,以后func的调用即是wrapper的调用

@InstanceDecorator(d1='dd11', d2='dd22')
def hello_func_without_params():
    print('----hello_func_without_params is executing------')

print('hello_func1 ===========', hello_func_without_params)            # 为一个闭包
hello_func_without_params()

@InstanceDecorator(d1='dd33', d2='dd44')
def hello_func_with_params(f1, f2):
    print('----hello_func_with_params is executing------')
    return f1 + f2
f12 = hello_func_with_params('aaa', f2='bbb')

过程分析:

装饰过程就是执行__call__的过程

装饰的过程中,可以进行适当操作,

装饰过程的最后是否return:

a. 没有return时,原被装饰函数func=None

b. 有return时,原被装饰函数func被覆盖为return的结果,通常为一个闭包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值