头图

Python学习,粗浅聊聊装饰器

通常情况下,给一个对象添加新功能有三种方式:

  • 直接给对象所属的类添加方法
  • 使用组合(在新类中创建原有类的对象,重复利用已有类的功能)
  • 使用继承(可以使用现有类的,无需重复编写原有类进行功能上的扩展)

一般情况下,优先使用组合,而不是继承,但是装饰器属于第四种,动态的改变对象从而扩展对象的功能。

一般装饰器的应用场景有打印日志、性能测试、事务处理、权限校验。

Python内置装饰器的工作原理

理解Python装饰器工作原理,首先需要理解闭包这一概念。闭包指的是一个函数嵌套一个函数,内部嵌套的函数调用外部函数的变量,外部函数返回内嵌函数,这样的结构就是闭包。

装饰器就是闭包的一种应用,但是装饰器参数传递的是函数。

简单的闭包示例:

def add_num(x):    def sum_num(y):        return x+y    return sum_num
add_num5 = add_num(5)total_num = add_num5(100)print(total_num)

注解:add_num外函数接受参数x,返回内函数sum_num,而内函数sum_num接受参数y,将和add_num外函数接受参数x相加,返回结果。

add_num5对象则是定义了add_num外函数接受的参数x为5,最后add_num5(100)返回的结果则是105。

装饰器的基本使用

简单计算函数运行时间装饰器示例:

def times_use(func):    def count_times(*args, **kwargs):        start = time.time()        result  = func(*args, **kwargs)        end = time.time()        print(end-start)        return result    return count_times
@times_usedef test_decorator():    time.sleep(2)    print("Test Decorator")test_decorator()

注解:这里将函数test_decorator作为参数,传入到times_use函数中,然后内部函数count_times则是会保留原有test_decorator函数代码逻辑。

在执行test_decorator前保存执行前时间,然后和执行后的时间进行比较,得出相应的耗时。

通过装饰器的好处则是能在保留原有函数的基础上,不用进行对原有函数的修改或者增加新的封装,就能修饰函数增加新的功能。

根据日志等级打印日志装饰器示例(带参数的装饰器):

def use_logging(level):    def decorator(func):        def wrapper(*args, **kwargs):            if level == "warn":                logging.warn("%s is running"% func.__name__)            result = func(*args, **kwargs)            print(result)             return result        return wrapper    return decorator
@use_logging("warn")def test_decorator():    print("Test Decorator")    return "Success"test_decorator()

计算函数运行时间的类装饰器示例:

class logTime:    def __init__(self, use_log=False):        self._use_log = use_log
    def __call__(self, func):
        def _log(*args, **kwargs):            start_time = time.time()            result = func(*args, **kwargs)            print(result)            end_time = time.time()            if self._use_log:                print(end_time-start_time)            return result        return _log
    @logTime(True)def test_decorator():    time.sleep(2)    print("Test Decorator")    return "Success"

functools wraps使用场景

使用装饰器虽然能在保存原有代码逻辑的基础上扩展功能,但是原有函数中的元信息会丢失,比如__name__, __doc__,参数列表。

针对这种情况可以使用functools.wraps,wraps也是一个装饰器,但是会将原函数的元信息拷贝到装饰器函数中。

具体使用方法:

from functools import wraps
def use_logging(level):    def decorator(func):        @wraps(func)        def wrapper(*args, **kwargs):
            if level == "warn":                logging.warn("%s is running"% func.__name__)            result = func(*args, **kwargs)            print(result)             return result        return wrapper    return decorator
@use_logging("warn")def test_decorator():    """" Test Decorator DocString""""    time.sleep(2)    print("Test Decorator")    return "Success"
print(test_decorator.__name__)print(test_decorator.__doc__)

注解:wraps装饰器将传入的test_decorator函数中的元信息拷贝到wrapper这个装饰器函数中,使得wrapper拥有和test_decorator的元信息。

关于装饰器的执行顺序

在日常业务中经常会使用多个装饰器,比如权限验证、登录验证、日志记录、性能检测等等使用场景。

所以在使用多个装饰器时,就会涉及到装饰器执行顺序的问题。

先说结论,关于装饰器执行顺序,可以分为两个阶段:(被装饰函数)定义阶段、(被装饰函数)执行阶段:

  • 函数定义阶段,执行顺序时从最靠近函数的装饰器开始,从内向外的执行;
  • 函数执行阶段,执行顺序时从外而内,一层层执行。

多装饰器示例:

def decorator_a(func):    print("Get in Decorator_a")
    def inner_a(*args, **kwargs):        print("Get in Inner_a")        result = func(*args, **kwargs)        return result    return inner_a
def decorator_b(func):    print("Get in Decorator_b")
    def inner_b(*args, **kwargs):        print("Get in Inner_b")        result = func(*args, **kwargs)        return result    return inner_b
@decorator_b    @decorator_adef test_decorator():    """test decorator DocString"""    print("Test Decorator")    return "Success"

运行结果:

Get in Decorator_aGet in Decorator_bGet in Inner_bGet in Inner_aTest Decorator

代码注解:上述函数使用装饰器可以相当于decorator_b(decorator_a(test_decorator()),即test_dcorator函数作为参数传入到decorator_a函数中,然后打印"Get in Decorator_a",并且返回inner_a函数,给上层decorator_b函数,decorator_b函数接受了作为参数的inner_a函数,打印"Get in Decorator_b",然后返回inner_b函数。

此时test_decorator(),即调用了该inner_b函数,inner_b函数打印"Get in inner_b",然后调用inner_a函数,inner_a打印了"Get in Decorator_a",最后调用test_decorator函数。这样从最外层看,就像直接调用了test_decorator函数一样,但是可以在刚刚的过程中实现功能的扩展。

更多Python技术干货内容欢迎前往公众号【Python编程学习圈】了解,关注即可领取大量学习资料以及教程,内容覆盖Python电子书、教程、数据库编程、Django,爬虫,云计算等等。

全球Python编程中文开发者的圈子,提供Python编程技术文章知识分享、技术专栏、原创视频教程、题库,内...

10 声望
1 粉丝
0 条评论
推荐阅读
终结 Python 原生字典?这个库要逆天改命了
字典是 Python 中基础的数据结构之一,字典的使用,可以说是非常的简单粗暴,但即便是这样一个与世无争的数据结构,仍然有很多人 "看不惯它" 。

Python编程学习圈阅读 303

封面图
数据结构与算法:二分查找
一、常见数据结构简单数据结构(必须理解和掌握)有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小)无序数据结构:集合、字典、散列表,无序数据结构省时间(读取时间快)复杂数据结构树、 堆图二...

白鲸鱼9阅读 5.3k

滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许3阅读 1.5k

搭个ChatGPT算法模型,从哪开始?
最近 ChatGPT 很火,火到了各行各业。记得去年更多的还是码农最新体验后拿它搜代码,现在各行各业都进来体验,问它咋理财、怎么写报告和给小孩起名。😂 也因此让小傅哥在头条的一篇关于 ChatGPT 的文章都有了26万...

小傅哥6阅读 1.2k

封面图
程序员适合创业吗?
大家好,我是良许。从去年 12 月开始,我已经在视频号、抖音等主流视频平台上连续更新视频到现在,并得到了不错的评价。每个视频都花了很多时间精力用心制作,欢迎大家关注哦~考虑到有些小伙伴没有看过我的视频,...

良许3阅读 1.3k

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 4.5k评论 1

PyCharm 激活破解教程, 2023 年 2 月亲测有用
本文分享一下PyCharm 2022.2.3 版本最新激活破解教程,注意不要使用太新的版本,都是 Jetbrains 产品,本文专门配上了 Pycharm 的图片,跟着下面教程一步一步来即可。

程序员徐公阅读 8.8k评论 1

全球Python编程中文开发者的圈子,提供Python编程技术文章知识分享、技术专栏、原创视频教程、题库,内...

10 声望
1 粉丝
宣传栏