Python装饰器

1. 闭包函数

如果在内部函数引用了外部函数里定义的对象(甚至在外层之外,但不是全局变量),那么此时内部函数就称之为闭包函数。闭包函数所引用的外部定义的变量叫做自由变量。

def count():
    a = 1
    b = 1

    def sum():
        c = 1
        # 此时a为自由变量
        return a + c

    return sum

闭包函数主要满足下面2点:

  • 函数内部定义的函数
  • 引用外部变量但非全局变量

2.装饰器

  1. Python装饰器本身也是一个函数,他可以在其他函数不需要做任何代码修改的前提下增加而外功能,装饰器的返回值也是一个函数对象(函数的指针)。
  2. 应用场景:插入日志,性能测试,事务处理,权限校验

2.1 函数装饰器

给函数添加计时功能

import time


def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)
    return wrapper


@decorator
def func():
    time.sleep(0.8)

func()

当执行func()函数时,func()函数的执行流程分析:

  1. 把func函数作为参数传给@符号后面的装饰器函数。
  2. 然后执行装饰器函数,并返回一个包装了的函数,同时改变原函数的指向,现在原函数指向了这个包装函数。
  3. 执行原函数,其实此时执行的是包装了的函数

2.2 带参数的函数装饰器

根据日志级别打印日志

import logging


def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warning":
                logging.warning("%s is running" % func.__name__)
            return func(*args)
        return wrapper
    return decorator


@use_logging(level="warning")
def foo(name='foo'):
    print("i am %s" % name)


foo()

# 输出:
WARNING:root:foo is running
i am foo

2.3 类装饰器

相比函数装饰器,类装饰器具有灵活度大,高内聚,封装性等优点。使用类装饰器还可以依靠内部的__call__方法,当使用@形式装饰器附加到函数时,就会调用此方法。

class Foo:

    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print("class decorator running")
        self._func()
        print("class decorator ending")


@Foo
def bar():
    print("func bar start")

bar()

注意:__call__()是一个特殊方法,它可以将一个类实例变成一个可调用对象

# f是类Foo的一个实例
f = Foo(func)
# 实现了__call__()方法后,f可以被调用
f()

2.4 装饰器链

一个Python函数如果被多个装饰器装饰,那么执行顺序是怎么样的

import time, logging


def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func()
        time.sleep(0.8)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper


def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warning":
                logging.warning("%s is running" % func.__name__)
            return func(*args)
        return wrapper
    return decorator


@use_logging(level="warning")
@decorator
def foo(name='foo'):
    print("i am %s" % name)

foo()

输出:
WARNING:root:wrapper is running
i am foo
0.8006932735443115

当我们对f传入参数1进行调用时,inner_b被调用了,他会先打印Get in inner_b,然后在inner_b内部调用了inner_a,所以会再打印Get in inner_a,然后再inner_a内部调用原来的f,并且将结果作为最终的返回总结:装饰器函数在被装饰函数定义好后立即执行从下往上执行函数调用时从上到下执行

def decorator_a(func):
    print('Get in decorator_a')

    def inner_a(*args, **kwargs):
        print('Get in inner_a')
        return func(*args, **kwargs)

    return inner_a


def decorator_b(func):
    print('Get in decorator_b')

    def inner_b(*args, **kwargs):
        print('Get in inner_b')
        return func(*args, **kwargs)

    return inner_b


@decorator_b  # f=decorator_b(f)
@decorator_a  # f=decorator_a(f)
def f(x):
    print('Get in f')
    return x * 2

f(1)

输出:
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

结论:多个装饰器执行顺序是从近到远依次执行

2.5 Python装饰器库:functools

使用functools.wraps(func)装饰器可以返回原函数的元信息,如name, 参数列表等

未使用该装饰器,foo.name为wrapper

import time, logging


def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func()
        time.sleep(0.8)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper


def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warning":
                logging.warning("%s is running" % func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@use_logging(level="warning")
def foo(name='foo'):
    print("i am %s" % name)
    print(foo.__name__)

foo()
输出:
WARNING:root:foo is running
i am foo
wrapper

使用该装饰器,打印foo.name为 foo

import time, logging, functools


def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func()
        time.sleep(0.8)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper


def use_logging(level):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if level == "warning":
                logging.warning("%s is running" % func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@use_logging(level="warning")
def foo(name='foo'):
    print("i am %s" % name)
    print(foo.__name__)

foo()
输出:
i am foo
foo
WARNING:root:foo is running
posted @ 2022-03-12 16:06  KB、渣科  阅读(39)  评论(0编辑  收藏  举报