Python装饰器介绍(一)

一. 预备知识

1.1 Python函数中的可变参数

  • 函数参数可以分为形参(必填参数, 默认参数, 可变参数)和实参(位置参数, 关键字参数)
  • 可变参数能够支持函数接收任意数量的参数(实参)
  • *args表示收集所有多余的位置参数到一个args元组中, 其中argsarguments的缩写, 
  • **kwags表示收集所有多余的关键字参数到一个kwargs字典中, 其中kwargskeyword arguments的缩写
# -*- coding: utf-8 -*-

def test1(*args, **kwargs):
    print(f"这是额外的位置参数: {args}")
    print(f"这是额外的关键字参数: {kwargs}")


t = ('a', 'b')
d = {'c': 'c', 'd': 'd'}
# *表示对元素解包; **表示对字典解包
test1(*t, **d)  # 等价于: test1("a", "b", c="c", d="d")

1.2 对闭包函数的理解

  • python中的函数有一些特点: 函数内可以定义函数; 函数可以作为参数被传递; 函数可以作为另一个函数的返回值
  • 闭包函数的本质其实是一个嵌套函数
  • 外层函数内层函数提供了一个运行环境, 内层函数被当成返回值返回给外层函数
  • 从python语法上, 可以通过函数的__closure__方法来判断一个函数是否为闭包函数
# -*- coding: utf-8 -*-

def test1():
    def inner1(m, n):
        return m + n

    return inner1


def test2(m):
    def inner2(n):
        return m + n

    return inner2


# [test1函数]不是闭包函数
print(test1.__closure__)  # 运行结果: None

# [test1函数]内的[inner1函数]是闭包函数
inner1 = test1()
print(inner1.__closure__)  # 运行结果: None

# [test2函数]不是闭包函数
print(test2.__closure__)  # 运行结果: None

# [test2函数]内的[inner2函数]是闭包函数
inner2 = test2(5)
print(inner2.__closure__)  # 运行结果: (<cell at 0x02FED430: int object at 0x7989D470>,)

二. 装饰器的介绍

2.1 装饰器是python中一类比较有特色的语法, 用来装饰函数和类

2.2 装饰器在不破坏原有函数结构的情况下, 可以进行功能扩展

2.2 使用装饰器, 可以增加代码的可读性, 让代码层次结构变得更加清晰

三. 装饰器的实现

3.1 编写一个最简单的装饰器 

实现代码:

# -*- coding: utf-8 -*-

# 被装饰函数
def myfunc():
    return "hello chinablue!"


# 装饰器: 在函数执行前
def mylog(func):
    def inner():
        print(f"这是执行函数前打印的信息")
        return func()

    return inner


print(mylog(myfunc)())

执行结果:

这是执行函数前打印的信息
hello chinablue!

3.2 编写一个通用装饰器

实现功能: 

  同一个装饰器可以装饰参数个数不同的函数

实现代码:

# -*- coding: utf-8 -*-

# 被修饰函数1
def myfunc1(a, b):
    return a + b


# 被修饰函数2
def myfunc2(a, b, c):
    return a + b + c


# 装饰器
def mylog(func):
  # 运用可变参数后, 可以装饰任何带参数的函数
def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner # myfunc1函数: 2个必填参数 mylog(myfunc1)(1, 2) # myfunc2函数: 3个必填参数 mylog(myfunc2)(1, 2, 3)

执行结果:

执行结果为: 3
执行结果为: 6

3.3 使用@语法糖方式调用装饰器

实现代码:

# -*- coding: utf-8 -*-

# 装饰器
def mylog(func):
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        print(f"执行结果为: {res}")
        return res

    return inner


@mylog
def add(a, b):
    return a + b


# 没有改变函数的调用方式
add(1, 2)

执行结果:

执行结果为: 3

3.4 装饰器会篡改原函数的属性

因为使用装饰器, 原函数(即被装饰函数)的地址指向了装饰器所定义的内部inner函数

# 打印add函数的属性
print(f"{add.__name__}")  # 执行结果: inner
print(f"{add.__doc__}")   # 执行结果: None

改进代码:

# -*- coding: utf-8 -*-

from functools import wraps


# 装饰器
def mylog(func):
    # 可以借助functools.warps函数, 避免属性篡改问题
    @wraps(func)
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        print(f"执行结果为: {res}")
        return res

    return inner


@mylog
def add(a, b):
    """
    功能: 实现加法运算
    :param a:
    :param b:
    :return:
    """
    return a + b


# 打印函数的属性
print(f"add函数的名字: {add.__name__}")
print(f"add函数的注释信息: {add.__doc__}")

add(1, 2)

执行结果:

add函数的名字: add
add函数的注释信息: 
    功能: 实现加法运算
    :param a:
    :param b:
    :return:
    
执行结果为: 3

3.5 带参数的装饰器

实现功能:

  实现一个可以设置日志等级的log装饰器

思路分析:

  为了让装饰器能够传参, 需要再嵌套一层函数

 实现代码:

# -*- coding: utf-8 -*-

from functools import wraps


# 装饰器
def mylog(level="INFO"):
    def outter(func):
        @wraps(func)
        def inner(*args, **kwargs):
            res = func(*args, **kwargs)
            print(f"[{level}]函数{func.__name__}的执行结果为: {res}")
            return res

        return inner

    return outter


@mylog(level="INFO")
def add(a, b):
    return a + b


@mylog(level="ERROR")
def sub(a, b):
    return a - b


# 打印函数的属性

add(1, 2)
sub(1, 2)

执行结果:

[INFO]函数add的执行结果为: 3
[ERROR]函数sub的执行结果为: -1

3.6 一个函数可以同时使用多个装饰器

# -*- coding: utf-8 -*-

from functools import wraps


# 装饰器1
def mylog1(func):
    @wraps(func)
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        print(f"执行结果为: {res}")
        return res

    return inner


# 装饰器2
def mylog2(level="INFO"):
    def outter(func):
        @wraps(func)
        def inner(*args, **kwargs):
            res = func(*args, **kwargs)
            print(f"[{level}]函数{func.__name__}的执行结果为: {res}")
            return res

        return inner

    return outter


@mylog2(level="DEBUG")
@mylog1
def add(a, b):
    return a + b


# 多个装饰器的执行顺序: 从近到远依次执行
add(1, 2)

使用函数调用方式调用装饰器

mylog2(level="ERROR")(mylog1(add))(1, 2)

四. 本文小结

1. 装饰器可以在不改变原函数代码,不改变原函数调用方式的情况下, 给函数添加扩展功能

2. 装饰器的本质就是一个嵌套函数, 如果装饰器需要接收参数, 则需要增加一层函数嵌套

3. 装饰器有两种调用方式: 1函数传参方式, 2使用@语法糖

4. 装饰器的外层函数接收的是被装饰函数, 返回的是内层函数

5. 装饰器的内层函数(即闭包函数)负责装饰被装饰函数

6. 装饰器的常用场景有: 打印日志, 性能测试, 权限验证等 

 

 

posted @ 2021-08-16 21:12  后来者2012  阅读(995)  评论(0编辑  收藏  举报