当前位置: 首页 > news >正文

Python笔记 - 利用装饰器设计注解体系

认识注解

注解(Annotation)是一种用于为代码添加元数据的机制。这些元数据可以在运行时被访问,用于为代码元素(如类、方法、字段等)提供额外的信息或指示。

由于Python中装饰器只能装饰类和方法,因此也只能为此二者提供额外的信息。

整体思路

通过Python中的装饰器实现注解功能,需要注意的是,此设计的缺陷在于只能装饰类和方法。

在Python中@语法糖,等价立即调用,参数为装饰的目标类或者函数:

classAnnotation:pass@Annotation

等价

Annotation()

由于Python作为动态语言,不涉及编译时期,我们去掉注解的声明周期,只提供作用范围,即作用在类上或者方法上,通过枚举类ElementType规范。

而对应的元注解为Target,即注解的注解,在Python中应该为装饰类装饰器的装饰器(有点绕)。

因为需要修改注解的原本行为,因此元注解的设计较为核心,当前阶段只涉及Target一个元注解,该注解应该具备如下功能:

  1. 接受一个ElementType作为参数,用于判断注解应该作用在类上还是函数上,或者皆可。
  2. 因为元注解的装饰对象一定是注解类(即类装饰器),因此元注解的目标对象为class类;所有注解类都应该默认实现Annotation接口。
  3. 由于Python中不具备Class类,即已知class A我们无法获取类A上是否存在注解,比如@AriaFor,因此元注解应该获取目标类A添加属性__annotation_list__用于存储注解实例对象,方便通过类A获取元信息。

设计

1. 基类Annotation

参考Java注解概念,此接口默认为注解的超类,所有注解都属于Annotation。

classAnnotation:@abstractmethoddefannotationType(self)->'Annotation':raiseNotImplementedError

2. 枚举类ElementType

此枚举类用于规范注解的作用范围,声明@Target时候必须指明作用范围,即@Target(ElementType.Type)。

classElementType(Enum):classType:def__init__(self,name,value,label):self.name=name self.value=value self.label=label TYPE=Type('type',type,'类')METHOD=Type('method',types.FunctionType,'函数')

Q:为什么枚举类内部还需要封装一个Type类?

这是因为当我们进行类型检查的时候,我们需要判断装饰的目标对象是函数还是类,如果不是规定的类型,则应该抛出异常,而此类型则为Type.value。

3. 参数类型校验装饰器@accepts

此为函数装饰器,用于校验函数的入参是否符合规范,这里用来检查ElementType。

defaccepts(*types):defcheck_accepts(f):defwrapper(*args,**kwargs):ifnotall(isinstance(arg,types)forarginargs[1:]):raiseTypeError("Argument %s is not of type %s"%(args,types))returnf(*args,**kwargs)returnwrapperreturncheck_accepts

Note:此装饰器并不通用,原因是对args[1:]进行了截断,也就是通常针对类方法(self,*args)这种,因为我们不需要校验第一个参数self。

4. (核心)元注解@Target

此注解本质为类装饰器,作为注解的元注解,他需要改变注解的一些基本行为,主要包括三个方法的重写:__init____new____call__

元注解只能作用在注解上,并且必须指明参数ElementType,否则无法正常工作。

4.1 源码

classTarget:@accepts(list,tuple,ElementType)def__init__(self,elementTypes:Union[List[ElementType],ElementType]):# 1. 检查列表或者元组类型是否正确ifisinstance(elementTypes,list)orisinstance(elementTypes,tuple):foreinelementTypes:ifnotisinstance(e,ElementType):raiseTypeError(f"@Target只能声明作用范围为ElementType,当前{type(e)}为不支持的类型。")# 2. 元组和ElementType需要转化为list列表形式ifisinstance(elementTypes,ElementType):elementTypes=[elementTypes]self.elementTypes=[e.valueforeinelementTypes]def__call__(self,cls):classAnnoProxy(cls,Annotation):defannotationType(self):returnclsdef__init__(self,*args,**kwargs):iflen(args)>1:raiseTypeError(f"@{self.__class__.__name__}只能接受一个args参数作为默认value,存在多个参数请使用字典。")iflen(args)==1:self.value=args[0]iflen(args)>0elsekwargs.get('value')super().__init__(*args,**kwargs)def__new__(_cls,*args,**kwargs):_instance=super(AnnoProxy,_cls).__new__(_cls)_instance.source=cls _instance.elementTypes=self.elementTypesiflen(kwargs)==0andlen(args)==1and(isinstance(args[0],type)orisinstance(args[0],types.FunctionType)):returnAnnoProxy.wrapper_target(_instance,args[0])else:# 其他情况则为 @AriaFor(123)或者@AriaFor(name="Tom")这种带参数的形式调用。_instance.elementTypes=self.elementTypesreturn_instancedef__call__(self,target):# 如果调用了__call__方法说明目标注解类的使用形式为带参数的类装饰器,即@AriaFor(123)这种# 此时 target 就是装饰对象,类或者函数returnAnnoProxy.wrapper_target(self,target)@staticmethoddefwrapper_target(_instance,target):support_types=[e.valueforein_instance.elementTypes]labels=[e.labelforein_instance.elementTypes]ifnotany(isinstance(target,s_type)fors_typeinsupport_types):raiseTypeError(f"@{_instance.source.__name__}无法装饰[{type(target).__name__}][{target.__name__}],此注解只能作用在[{'和'.join(labels)}]上")target.__annotation_list__.append(_instance)ifhasattr(target,'__annotation_list__')elsesetattr(target,'__annotation_list__',[_instance])returntargetreturnAnnoProxy

4.2 说明

作为元注解,他的装饰对象是已知固定的,即注解类;并且一定是带参数的类装饰器,因此定义时候__init__方法接受固定类型参数ElementType。

当使用元注解装饰一个注解的时候,如下:

@Target(ElementType.Type)classAliaFor:pass

应该固定返回一个代理类,即注解的代理类,该类的父类为目标类AliasFor,以及基类Annotation,因此__call__方法的返回值为注解的代理类,即AnnoProxy

通过此代理类,我们修改注解的基本行为,主要通过定义:__init____new____call__方法。

由于我们在使用注解的时候存在两种情况,这两种情况在类装饰器中的表现完全不同,因此必须分开讨论:

使用方式一:

@AliasForclassA:pass

使用方式二:

@AliasFor(123)classA:pass# 或者@AliasFor(name="Tom")classA:pass

方式一是不带参数的类装饰器,此方法执行目标类A作为参数传递的时候作为__new__方法的参数传入。

方式二则是带参数的类装饰器,此方法执行目标类A讲作为参数传递的时候作为__call__方法的参数传入。

效果展示

当我们定义注解的时候,我们只需要通过元注解@Target并且指定作用范围即可,可以是单个ElementType,也可以同时支持类和函数。

当携带参数时,即注解的属性值,如果属性值只有一个,并且名称为value,可以省略不写。

1. 不带参数的使用

声明一个注解:

@Target(ElementType.TYPE)classAliaFor:pass

使用注解:

@AliaForclassA:passif__name__=='__main__':# 获取类A上的注解信息forainA.__annotation_list__:# 查看注解实例print(a)# 查看注解的类型print(a.annotationType())# 查看注解是否为Annotation的实例对象print(isinstance(a,Annotation))

输出:

可以看到,返回的是一个代理类对象,即AnnoProxy,且属于Annotation类。

2. 带参数的使用

2.1 只有一个参数,且参数名称为value

声明一个注解:

@Target(ElementType.TYPE)classAliaFor:def__init__(self,value):self.value=value

使用注解:

@AliaFor(123)classA:passif__name__=='__main__':# 获取类A上的注解信息forainA.__annotation_list__:# 查看注解实例print(a)# 查看注解的类型print(a.annotationType())# 查看注解是否为Annotation的实例对象print(isinstance(a,Annotation))# 查看注解的属性值print(a.value)

输出:

2.2 注解属性值

声明一个注解:

@Target(ElementType.TYPE)classAliaFor:def__init__(self,name,age):self.name=name self.age=age

使用注解:

@AliaFor(name="Tom",age=18)classA:passif__name__=='__main__':# 获取类A上的注解信息forainA.__annotation_list__:# 查看注解实例print(a)# 查看注解的类型print(a.annotationType())# 查看注解是否为Annotation的实例对象print(isinstance(a,Annotation))# 查看注解的属性值print(a.name)print(a.age)

输出:

3. 错误作用范围异常

声明注解作用在类上:

@Target(ElementType.TYPE)classAliaFor:pass

错误的使用:

classA:@AliaFordefadd(self):pass

输出:

4. 使用工具类

classAnnotationUtils:@staticmethoddefgetAnnotations(source:type)->Union[List[Annotation],None]:returnsource.__annotation_list__ifhasattr(source,'__annotation_list__')elseNone@staticmethoddefgetAnnotation(source:type,annotation_type:type)->Union[Annotation,None]:ifAnnotationUtils.getAnnotations(source)isNone:returnNonereturnnext((aforainAnnotationUtils.getAnnotations(source)ifisinstance(a,annotation_type)),None)@staticmethoddefisAnnotationPresent(source:type,annotation_type:type):returnAnnotationUtils.getAnnotation(source,annotation_type)isnotNone@staticmethoddefgetAnnotationAttributes(annotation:Annotation)->dict:return{k:vfork,vinannotation.__dict__.items()ifnotk.startswith('_')}@staticmethoddefgetAnnotationAttribute(annotation:Annotation,attribute_name:str):returnAnnotationUtils.getAnnotationAttributes(annotation).get(attribute_name,None)

声明一个注解:

@Target([ElementType.TYPE,ElementType.METHOD])classAliaFor:def__init__(self,name,age):self.name=name self.age=age

使用注解:

@AliaFor(name="Tom",age=18)classA:@AliaFordefadd(self):passif__name__=='__main__':print(AnnotationUtils.getAnnotations(A))print(AnnotationUtils.getAnnotation(A,AliaFor))print(AnnotationUtils.isAnnotationPresent(A,AliaFor))print(AnnotationUtils.getAnnotationAttributes(AnnotationUtils.getAnnotation(A,AliaFor)))print(AnnotationUtils.getAnnotationAttribute(AnnotationUtils.getAnnotation(A,AliaFor),'name'))

输出:


🔗参考链接

[1]:官方文档函数装饰器 PEP 318
[2]:官方文档类装饰器 PEP 3129
[3]:博客 # # Python笔记 - 函数、方法和类装饰器

http://www.cnnetsun.cn/news/1290175.html

相关文章:

  • weixin225基于微信小程序的汽车销售系统的设计与实现springboot(文档+源码)_kaic
  • PyCaret批量预测:处理大规模推理任务的终极指南
  • Xcode 16及升级 Xcode 26 编译弹窗问题、编译通过无法,编译通过打包等问题汇总
  • 【JEECG 组件扩展】JUpload上传组件扩展拖拽区域上传
  • 看懂 SAP Memory Management:从用户会话、工作进程到系统调优的完整脉络
  • 智慧校园可视化大屏实战:从教室使用率到安全预警的8个真实案例
  • 快速获取天地图Key的完整指南(onmap)
  • Redis可视化利器——RedisDesktopManager的安装与实战指南
  • F2FS源码探秘-1.5 [NAT结构解析] Node Address Table的内存管理与优化策略
  • 手把手教你用AT32F415搭建低成本CAN记录仪(含FATFS文件系统移植)
  • 深入剖析STM32启动流程:从Flash到SRAM的代码执行之旅
  • Resolving NotImplementedError in YOLO: How to Handle Unsupported ‘_new‘ Mode for Undefined Tasks
  • 基于高德地图JSAPI的驾车路线规划实战:从基础集成到策略优化
  • 从B站到抖音:不同平台视频GOP设置的秘密(附ffprobe分析技巧)
  • Google Nano-Banana图像编辑实战:如何用蒙版和多图参考生成专业级作品?
  • 从零构建CPU:基于Verilog与FPGA的简易计算机系统设计实践
  • VMware vSphere时间同步终极指南:如何让ESXi主机精准同步Windows NTP服务器
  • 华为交换机M-LAG实战:从基础配置到高可用部署
  • Cover Letter 实战指南:从查重到投稿的科研沟通艺术
  • 树莓派4B变身安卓盒子:LineageOS 18.1刷机+远程控制全攻略(附避坑指南)
  • Type-C接口CC引脚全解析:从电阻配置到设备识别(附常见问题排查)
  • 网络工程师必看:等价路由、浮动路由、路由汇总的实战配置与避坑指南
  • 【半导体先进工艺制程技术系列】应变硅:从能带工程到速度提升的工艺密码
  • Piccolo Engine物理调试渲染器使用指南:Windows平台专属功能解析
  • 5个理由告诉你为什么OpenInTerminal是macOS开发效率的终极神器
  • AnyPixel.js终极指南:从基础按钮到创新交互元素的完整扩展教程
  • 如何快速掌握xhyve内存管理:从虚拟地址到物理地址的完整映射指南
  • AnyPixel.js终极指南:如何用创新交互式显示技术赋能教育领域
  • 终极TensorFlow NMT工具函数实战指南:从misc_utils到vocab_utils的完整教程
  • T5模型终极优化指南:7个技巧显著提升推理速度与降低内存占用