你不知道的Python大杀器,装饰器中的多重使用方法!_python中定义修饰器的时候为什么不定义多重

本文介绍了Python的学习资源,包括从基础到各方向的技术点、开发工具推荐、视频教程、实战练习以及面试准备。特别强调了装饰器的使用和与Java注解的对比,以帮助读者建立全面的Python技能体系。
摘要由CSDN通过智能技术生成

最后

Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

👉Python所有方向的学习路线👈

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

👉Python必备开发工具👈

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

👉Python全套学习视频👈

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

👉实战案例👈

学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。

因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。

👉大厂面试真题👈

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

foo = log(foo) 装饰器的 @ 标记等于告诉 Python 解释器:”你把 @ 下一行的函数作为参数传给该装饰器,然后把返回值赋值给该函数”, 相当于执行 foo = log(foo)。 当我们调用 foo() 相当于是调用 log(foo)()。

对于带参数的装饰器 file_lock(name,block), 我们分成两个阶段理解,回顾一下 file_lock 的三层函数实现,我们在第二层定义了一个 wrapper 函数,该函数接受了一个 func 参数,随后我们在 file_lock 的最后将其 return, 我们可以这样认为:

你不知道的Python大杀器,装饰器中的多重使用方法!

第一阶段执行了最外层的函数 file_lock, 返回了 wrapper。

第二阶段,使用 wrapper 装饰 test, 第二阶段我们已经熟悉理解了。

实际上, 只有第一阶段是多执行的。 由于我们多给它加了一个括号, Python 解释器自然会去执行该函数, 该函数返回另一个装饰函数, 这样就到了第二阶段。

Python 解释器希望我们这样去理解,否则三层函数的写法很让人崩溃。后续我们继续探索装饰器能不能实现相同的功能,但是摆脱编写三层函数的噩梦。

以上分析了带参数和不带参数的装饰器的区别,及如何在心里去理解与接受这种写法, Python 通过语法糖, 函数之上的装饰器定义 代替蠢笨的手动装饰调用。

我们可以实现让复杂的装饰器,提供极其优雅的使用方式给调用方还是让人鼓舞的,事实上, Python 的框架中大量的使用了装饰器,也说明了装饰器的强大与优雅。

3. Python 装饰器方法的执行时机与顺序

Python 是解释执行的语言。做一个小实验,以上例子是先定义 log 装饰器,而后再使用 log 装饰器,让我们换一下顺序:

你不知道的Python大杀器,装饰器中的多重使用方法!

毫无疑问,这样会报语法错误, Python 是从 Python 文件从上到下执行解释, 只有已经定义了 log, 才能使用它。

在 Python 中,函数是一等公民也是对象,定义函数也是在声明一个函数对象:

def foo():
 pass

def foo() 就是在声明一个函数对象,foo 即是该函数对象的引用, 我们可以额外定义该对象的属性, 通过 dir(foo) 查看一下该函数对象有哪些属性,其实是和类实例对象没有区别的。

以上提过 test 被 log装饰 后, test() 等同于 log(test)(), Python 装饰器解释执行完 @log def test(),等同于 test=log(test) 此时 test 引用的函数对象是 log 装饰后的函数对象。

你不知道的Python大杀器,装饰器中的多重使用方法!

3.1 装饰器的执行顺序

实际开发中我们经常会使用多个装饰器,如果读者理解了 3.0 及以上的函数对象的概念,其实能猜出来装饰器的装饰顺序是从上往下执行的:

例如:foo = a(b(c(foo))) 实际的代码执行顺序是 c->b->a 。

你不知道的Python大杀器,装饰器中的多重使用方法!

以上我们用日志装饰器和文件锁装饰器介绍了装饰器的使用,并且讨论了带参数及不带参数装饰器的区别。

其中”三层函数”的定义方式可读性非常差,在下一节将重点讨论如何使用类实现装饰器简化三层装饰器的逻辑,减少相似代码的编写。

4. 装饰器类的设计

在本节中,我们重点优化三层装饰器的编写,除此之外,笔者在实际开发中还发现了其他常见的需求。例如:

  1. 暂存装饰器的参数。 期望通过被装饰函数找到装饰器参数, 笔者在自动化测试中就使用装饰器定义测试 case, 需要在 case 中配置元数据信息,在实际的执行引擎部分,访问该元数据信息,就是将元数据信息放到函数对象中;

  2. 注册被装饰函数对象。 例如某些 web 框架,注册 handler, 需要在装饰器中实现某些注册逻辑。

从以上内容出发,可以看到装饰器的逻辑有某些通用的部分,然而以上装饰器的例子都是通过函数实现的, 但函数在内部状态、继承等方面明显不如类,所以我们尝试使用类实现装饰器,并尝试实现一个通用的装饰基类。

4.1 思路

Python 提供了很多奇异方法,所谓的奇异方法是指,只要你实现了这个方法,就可以使用 Python 的某些工具方法,例如实现了 __ len__ 方法,可以使用 len() 来获取长度,实现了 __ iter__ 可以使用 iter 方法返回一个迭代器,其他方法还有 “__eq__”、”__ne__”、 “__next__” 等。 其中当实现 __ call__ 方法时, 类可以被当做一个函数使用,例如以下示例:

你不知道的Python大杀器,装饰器中的多重使用方法!

是否也可以使用 Python 的这个特性实现装饰器呢?

答案是可以的,让我们来实现一个装饰基类, 解决以上的痛点:

你不知道的Python大杀器,装饰器中的多重使用方法!

BaseDecorator 实现的并不是具体的某个装饰器逻辑,它可以作为装饰器类的基类,之前我们曾分析编写装饰器通用的需求已经痛点。先具体讲解这个类的实现,而后在讨论如何使用。

  • @1__call__ 函数签名,*_ 代表忽略变长的位置参数,只接受命名参数。实际的装饰器中,一般都是使用命名参数,代码可读性高;
  • @2__call__ 本身的实现逻辑委托给了 do_call 方法,主要是考虑BaseDecorator 作为装饰基类,需要提供某些工具方法及可扩展方法,但是__ call__ 方法本身无法被继承,所以我们退而求次,将工具方法封装在自定义方法中,子类还是需要重新实现__ call__, 并调用 do_call 方法, do_call 方法的签名和__ call__ 相同;
  • @3 functools 提供了 wraps 装饰器,之前我们分析过 Python 是使用装饰后的函数对象替换之前的函数对象达到装饰的效果。

可能有人会有疑问,如果之前的函数对象有一些自定义属性呢? 装饰后的新函数会不会丢掉?

答案是肯定的, 我们可以访问之前的函数对象,给其设置属性,这些属性会被存储在对象的__ dict__ 字典中, 而 wraps 装饰器会把原函数的__ dict__拷贝到新的装饰后的函数对象中, 因此 wraps 装饰后,就不会丢掉原有的属性,而不使用则一定会丢掉。 感兴趣的读者可以点开 wraps 装饰器,看一下具体实现逻辑;

  • @4 在本节开始,我们提出装饰器的通用需求,其中之一是需要将装饰器的参数存放到被装饰的函数中,_add_dict 方法便是将装饰器参数设置到原函数以及装饰后的函数中;
  • @5 invoke 负责实现具体的装饰逻辑,例如日志装饰器仅仅是打印日志,那么该方法实现就是打印日志以及调用原函数。文件锁装饰器,则需要先获取锁后再执行原函数,具体的装饰逻辑在该方法中实现, 具体的装饰器子类应该重写该方法。下一节我们将继承该 BaseDecorator 重写以上的日志及文件锁装饰器;
  • @6 invoke 方法是装饰函数调用时被触发的, 而 wrapper 方法只会被触发一次,当 Python 解释器执行到 @log 时,会执行该装饰器的 wrapper 方法。相当于函数被定义的时候,执行了 wrapper 方法。在该方法内可以实现某些注册功能,将函数和某些键值映射起来放到字典中,例如 web 框架的 url 和 handler 映射关系的注册。

BaseDecorator 抽出来了 invoke,wrapper 目的是让子类装饰器可以在这两个维度上扩展,分别实现装饰及某些注册逻辑,在下一节我们将尝试重写日志及文件锁装饰器,更直观的感受 BaseDeceator 给我们带来的便利。

4.2 重写日志及文件锁装饰器

你不知道的Python大杀器,装饰器中的多重使用方法!

  • @1. invoke 方法中包括原函数以及原函数的输入参数,该输入参数不是装饰器的参数信息;
  • @2. 通过 func 可以访问到装饰器中定义的 desc 参数信息;
  • @3. 创建装饰器实例便可以像之前一样使用 @log,需要注意的是该装饰类变成单例,在定义装饰逻辑的时候,不要轻易在 self 中储存变量。

通过重写日志装饰器, 摆脱了三层函数的噩梦, 成功的分离了装饰器的基本代码以及装饰逻辑代码,我们可以更加聚焦于装饰逻辑的核心代码编写,同时可以通过原函数访问装饰器中输入的参数,比如我们可以访问到日志装饰器的 desc。

以下我们再重写文件锁装饰器:

你不知道的Python大杀器,装饰器中的多重使用方法!

  • @1. 可以通过 func 访问到装饰器中定义的 name 参数;
  • @2. 把参数传给 do_call 委托执行.
  • @3. 创建文件锁实例,其他位置就可以使用 @file_lock 了.

使用新的装饰基类后, 编写新的装饰器子类是非常轻松方便的,不需要再定义复杂的三层函数,不需要重复的设置装饰器参数.如果我们在项目中大量使用装饰器,不妨使用装饰基类,统一常见的功能需求。

装饰器的更多用法还需要读者去发掘,但是熟悉 Java 的同学

一定熟悉 Aop 的理念, 笔者深受 Java 折磨多年, 对 Aop 也几分偏爱。在我看来, Python 的装饰器是 Java 中的注解加 Aop 的结合。

下一节我们将横向对比 Java 注解与 Python 装饰器的相似点, 论证文章开头我们留下的一个论点。

5. 对比 Java 的注解

之所以对比 Java 注解,主要是笔者想从 Java 的某些用法得到一些借鉴与参考, 以便于我们应用到 Python 中,通过两种语言的对比可以让我们更深刻的理解语言设计者添加该特性的初衷,以便更好的使用该特性。更重要的是,我们面对不同语言的异同会有更大的包容性, 站在欣赏的角度去对比思考,对于我们快速掌握新的语言十分有益。本节绝不是为了争吵两种语言的优劣, 更不想挑起语言的战争。

装饰器和注解最直观的相似点可能就是 @ 符号了, Python 使用相同的符号对于 Java 程序员是一种”关照”。 因为 Java 程序员对于注解有一种特殊的迷恋, 第三方框架就是使用眼花缭乱的注解 帮助 Java 程序员实现一个个神奇的功能。而装饰器也是可以胜任的。

Java 的注解本身只是一种元数据配置,在没有注解之前, 如果实现相同的元数据配置只能依赖于 xml 配置, 有了注解之后,我们可以把元数据配置和代码放到一起,这样更加直观也更便于修改,至于某些人说 xml 配置可以省去编译打包, 其实在笔者经历的项目中,不论是改代码还是改配置都是需要重新走发布流程, 严禁直接修改配置重启程序(除极特殊情况)。

注解和注解解释器是密不可分的,定义注解之后,首先就应该想到如何定义解释器,读取注解上的元数据配置,使用该元数据配置做什么。

最常见的是使用方式是使用注解注册某些组件,开启某项功能。例如 Spring 中使用 Component 注册 bean,使用 RequestMapping 注册 web url 映射, Junit 使用 Test 注册测试 Case, Spring boot 中使用 EnableXXX 开启某些扩展功能等等。

注解解释器首先需要获取到 Class 对象,使用反射获取到注解中的元数据配置,然后实现”注册”、 “开关”逻辑。

以上在我们实现的解释器基类中,也实现了类似的功能,我们把装饰器的参数存放到具体的函数对象中, 等同于注解的元数据配置, 读者也可以扩展, 添加一个标记, 标记该函数对象确实被某装饰器装饰过。 这样便能像 Java 一样轻松的实现某些注册或者开关功能。

除此之外,注解作为元数据配置,可以作为 Aop 的切面,这也是注解被广泛使用的原因, 注解可以配置在类、属性、方法之上, “注册” 功能一般是配置在类上, 如果使用注解切面,需要将注解配置在方法之上。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值