前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python装饰器

Python装饰器

作者头像
雪飞鸿
发布2022-01-06 14:41:19
5250
发布2022-01-06 14:41:19
举报
文章被收录于专栏:me的随笔

Python中的装饰器是一个用于修改类或者函数功能的可调用对象(callable),函数或者实现了__call__方法的类都可以看作是可调用对象。Python中装饰器分为两大类:

  • 函数装饰器
  • 类装饰器

函数装饰器

最简单的装饰器

Python中最简单的装饰器是一个嵌套函数。举例,使用装饰器函数elapsed来统计函数执行耗时:

代码语言:javascript
复制
# _*_ coding=utf8 _*_
​
import time
import logging
​
logging.basicConfig(level=logging.INFO)
​
​
def elapsed(func):
    """统计函数执行耗时"""
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}')
    return wrapper
​
​
def delay():
    logging.info('delay...')
    time.sleep(0.2)
​
​
# 装饰器就是普通函数,可以像普通函数那样直接调用
elapsed(delay)()
​

输出如下:

代码语言:javascript
复制
INFO:root:delay...
INFO:root:exec delay elapsed:0.20386290550231934

对于装饰器Python在语言层面给予了支持,对上面代码做如下修改:

代码语言:javascript
复制
@elapsed
def delay():
    logging.info('delay...')
    time.sleep(0.2)
​
​
delay()​

输出入下:

代码语言:javascript
复制
INFO:root:delay...
INFO:root:exec delay elapsed:0.21143507957458496

使用装饰器后,打印函数信息,无法输出原函数信息,执行print(delay),输出<function elapsed.<locals>.wrapper at 0x0000018E8AFA4E50>

这里可以使用functools模块来解决:

代码语言:javascript
复制
import functools
​
def elapsed(func):
    """统计函数执行耗时"""
    @functools.wraps(func)
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}')
    return wrapper​

再次执行print(delay),输出<function delay at 0x00000186651B4E50>

装饰器可以被多次使用:

代码语言:javascript
复制
@elapsed
@elapsed
def delay():
    logging.info('delay...')
    time.sleep(0.2)
​
​
delay()

输出如下:

代码语言:javascript
复制
INFO:root:delay...
INFO:root:exec delay elapsed:0.21126818656921387
INFO:root:exec wrapper elapsed:0.21126818656921387

上述代码等价于如下调用方式:

代码语言:javascript
复制
def delay():
    logging.info('delay...')
    time.sleep(0.2)
​
​
# 装饰器就是普通函数,可以像普通函数那样直接调用
# 这里多次调用装饰器函数
elapsed(elapsed(delay))()​

调用堆栈如下:

代码语言:javascript
复制
"""
logging.info() # wrapper2
logging.info() # wrapper1
logging.info() # delay
delay()
wrapper1() # func=delay
wrapper2() # func=wrapper1
elapsed(wrapper1) # 返回新的wrapper,其中func指向wrapper1,这里记录为wrapper2
elapsed(delay) # 返回wrapper,其中func指向delay函数,这里记录为wrapper1
"""
带参数的装饰器

上节中提到的较为简单的装饰器是一个嵌套函数,带有参数的装饰器也是嵌套函数,只不过多嵌套一层:

代码语言:javascript
复制
# _*_ coding=utf8 _*_

import logging
import functools

logging.basicConfig(level=logging.DEBUG)


def exec(count):
    def exec_wrapper(func):
        @functools.wraps(func)
        def func_wrapper():
            for i in range(count):
                logging.info(f'exec {func}')
                func()
        return func_wrapper
    return exec_wrapper


@exec(3)
def func():
    pass


func()

输出如下:

代码语言:javascript
复制
INFO:root:exec <function func at 0x000001A783724E50>
INFO:root:exec <function func at 0x000001A783724E50>
INFO:root:exec <function func at 0x000001A783724E50>

上述示例中的无法直接给函数func传递参数,否则会抛异常:TypeError: func() takes 0 positional arguments but 1 was given,因为加上装饰器后,再调用func相当于调用func_wrapper函数。想要给原函数传递参数,需对装饰器做如下改造:

代码语言:javascript
复制
def exec(count):
    def exec_wrapper(func):
        @functools.wraps(func)
        def func_wrapper(*args, **kwargs):
            for i in range(count):
                logging.info(f'exec {func}')
                func(*args, **kwargs)
        return func_wrapper
    return exec_wrapper

func_wrapper函数中添加*args**kwargs两个参数,也可根据需要来添加其中某一个。

类装饰器

类装饰器与函数装饰器类似,只是类装饰器中要实现__call__方法:

代码语言:javascript
复制
class Elapsed:
    def __init__(self, func):
        self.__func = func

    def __call__(self, *args, **kwargs):
        start_time = time.time()
        self.__func(*args, **kwargs)
        end_time = time.time()
        logging.info(
            f'exec {self.__func.__name__} elapsed:{end_time-start_time}')
        
        
@Elapsed
def func(secs=0.1):
    time.sleep(secs)

装饰器不仅可以用于函数上,也可以用在类上,这里以类装饰器为例:

代码语言:javascript
复制
# _*_ coding=utf8 _*_

import logging
import time

logging.basicConfig(level=logging.DEBUG)


class LogClassName:
    def __init__(self, cls):
        self.__cls = cls

    def __call__(self, *args, **kwargs):
        logging.info(f'current class name: {self.__cls.__name__}')
        return self.__cls(*args, **kwargs)

    def __str__(self):
        return f'{self.__cls}'


@LogClassName
class Info:
    pass


logging.info(Info)
Info()

Python中的装饰器与装饰器模式

Python中的装饰器和装饰器模式有着相同的目的:在不修改原有功能代码的基础上对其做扩展。

Python在语言层面对与装饰器给与了支持,相对比较简洁,经典的装饰器模式在编码实现上通常比Python装饰器有更多的代码量。Python装饰器要明确的作用域某个函数或类上,装饰器模式则是针对某种类型的方法做扩展,具体扩展的对象在运行时才确定。此外,装饰器模式可以作为面向对象中继承的替代。

二者有相同的目的,但实现方式不同,Python装饰器可以看作是静态扩展,装饰器模式是动态扩展。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 函数装饰器
    • 最简单的装饰器
      • 带参数的装饰器
      • 类装饰器
      • Python中的装饰器与装饰器模式
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档