7.Python中的装饰器(Decorator)

《Python编程的术与道:Python语言进阶》视频课程
《Python编程的术与道:Python语言进阶》视频课程链接:https://edu.****.net/course/detail/28618

装饰器 (Decorator)

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外的功能。装饰器的返回值也是一个函数/类对象。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。

它经常用于有切面(aspect)需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。

def div(a,b):
    print(a/b)
div(2,4)
0.5
def div(a,b):
    if a<b:
        a,b = b,a
    print(a/b)
div(2,4)
2.0
def div(a,b):
    print(a/b)

def smart_div(func):

    def inner(a,b):

        if a<b:
            a,b = b,a
        return func(a,b)

    return inner
div_new = smart_div(div)

div_new(2,4)
2.0

简单的装饰器

装饰器包装一个函数,修改其行为。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_whee():
    print("Whee!")
say_whee = my_decorator(say_whee)
say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

语法糖 (Syntactic Sugar):

Python允许通过@符号以更简单的方式使用装饰器。 下面的示例与上面的装饰器示例完全相同:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_whee():
    print("Whee!")
say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

因此,@my_decorator只是表示say_whee = my_decorator(say_whee)的一种简单方法。 这是将一个装饰器应用于一个函数的方法。

装饰器中@functools.wraps作用

装饰器在运行期对函数进行一些外部功能的扩展。但是在使用过程中,由于装饰器的加入导致解释器认为函数本身发生了改变,在某些情况下比如测试时会导致一些问题。Python 通过 functool.wraps 为我们解决了这个问题:在编写装饰器时,在实现前加入 @functools.wraps(func) 可以保证装饰器不会对被装饰函数造成影响。主要是不改变使用装饰器原有函数的结构(如__name__, __doc__)

不使用@wraps装饰器时,__name____doc__输出的内容

def decorator(func):
    """this is decorator __doc__"""
    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__:  ", test.__doc__)
__name__:  wrapper
__doc__:   this is wrapper __doc__

对test()方法进行装饰时候,实际上是调用test = decorator(test)
返回的是wrapper方法的引用,也就是让test指向了wrapper方法,所以调用test.__name__, 实际上是wrapper.__name__,这可能会造成后面查找该方法的名字和注释的时候会得到装饰器的内嵌函数的名字和注释。

使用@wraps装饰器解决这个问题

from functools import wraps
def decorator(func):
    """this is decorator __doc__"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__:  ", test.__doc__)
__name__:  test
__doc__:   this is test __doc__
import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator

计时函数

首先创建一个@timer装饰器。 它将测量一个函数执行所需的时间并将时间打印到控制台。

import functools
import time

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer

@timer
def waste_some_time(num_times):
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])
waste_some_time(1)
Finished 'waste_some_time' in 0.0049 secs
waste_some_time(999)
Finished 'waste_some_time' in 3.0510 secs
上一篇:小白学 Python 爬虫(21):解析库 Beautiful Soup(上)


下一篇:Task 03:字典、元组、布尔类型、读写文件