python装饰器

1. 装饰器的定义

就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。

装饰器的功能特点:

  1. 不修改已有函数的源代码
  2. 不修改已有函数的调用方式
  3. 给已有函数增加额外的功能

2.装饰器代码

# 添加一个登录验证的功能
def decorator(fn):
    def inner():
        print("请先登录....")
        fn()
    return inner


def comment():
    print("发表评论")

# 使用装饰器来装饰函数
comment = decorator(comment)
comment()

说明:

  • 闭包函数有且只有一个函数类型参数,这样定义的闭包函数才是装饰器。
  • 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。

执行结果:

请先登录....
发表评论

 

3. 装饰器的语法糖写法

Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰

# 添加一个登录验证的功能
def decorator(fn):
    def inner():
        print("请先登录....")
        fn()
    return inner

@decorator
def comment():
    print("发表评论")

# 使用装饰器来装饰函数
comment()

说明:

  • @decorator 等价于 comment = decorator(comment)
  • 装饰器的执行时间是加载模块时立即执行。
  • 装饰完后,comment方法实际上就是装饰器返回的inner方法

4. 通用装饰器

# 通用装饰器
def decorator(fn):
    def inner(*args, **kwargs):
        print("请先登录....")
        res = fn(*args, **kwargs)
        return res
    return inner

@decorator
def comment():
    print("发表评论")

@decorator
def calculate(a, b):
    return a + b

# 使用装饰器来装饰函数
comment()
print(calculate(1,2))

执行结果:

请先登录....
发表评论
请先登录....
3

Process finished with exit code 0

说明:

  • 同用装饰器就是内部函数使用可变参数且有返回值的装饰器,因为可变参数可以接收任何参数
  • res = fn(*args, **kwargs) 内部函数这里的*args, **kwargs,是将元组args解包为位置参数,将字典kwargs解包为关键字参数

5. 多个装饰器的使用:

def make_div(func):
    """对被装饰的函数的返回值 div标签"""
    def inner(*args, **kwargs):
        return "<div>" + func(*args, **kwargs) + "</div>"
    return inner


def make_p(func):
    """对被装饰的函数的返回值 p标签"""
    def inner(*args, **kwargs):
        return "<p>" + func(*args, **kwargs) + "</p>"
    return inner


# 装饰过程: 1 content = make_p(content) 2 content = make_div(content)
# content = make_div(make_p(content))
@make_div
@make_p
def content():
    return "人生苦短"

result = content()

print(result)

执行结果:

<div><p>人生苦短</p></div>

Process finished with exit code 0

说明:

  • 多个装饰器装饰过程为由内到外装饰,上面的例子先make_p装饰,然后用make_div装饰

6. 带参数的装饰器

# 添加输出日志的功能
def logging(flag):

    def decorator(fn):
        def inner(num1, num2):
            if flag == "+":
                print("--正在努力加法计算--")
            elif flag == "-":
                print("--正在努力减法计算--")
            result = fn(num1, num2)
            return result
        return inner

    # 返回装饰器
    return decorator


# 使用装饰器装饰函数
@logging("+")
def add(a, b):
    result = a + b
    return result


@logging("-")
def sub(a, b):
    result = a - b
    return result

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

result = sub(1, 2)
print(result)

说明:

  • 使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回是装饰器,因为 @ 符号需要配合装饰器实例使用

7. 类装饰器

装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。

类装饰器示例代码:

class Check(object):
    def __init__(self, fn):
        # 初始化操作在此完成
        self.__fn = fn

    # 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
    def __call__(self, *args, **kwargs):
        # 添加装饰功能
        print("请先登陆...")
        self.__fn()


@Check
def comment():
    print("发表评论")


comment()

说明:

  • @Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
  • 装饰器执行完comment变量就变成一个Check的一个实例对象
  • 要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
  • 在call方法里进行对fn函数的装饰,可以添加额外的功能。

执行结果:

请先登陆...
发表评论

 

@wraps()语法糖(了解)
因为装饰器实质是就是一个函数,是一个被修饰过函数,他与原来未被修饰的函数是两个不同的函数对象。

所以,这个装饰器丢失了原来函数对象的一些属性,比如:__name__,__doc__等属性。使用wraps语法糖可以保留这些属性。

 

2. 小结

  • 想要让类的实例对象能够像函数一样进行调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable)
  • 类装饰器装饰函数功能在call方法里面进行添加

扩展:其实普通函数里面也实现了call方法

posted @ 2021-01-05 21:27  foreast  阅读(54)  评论(0编辑  收藏  举报