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

认识注解

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

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

整体思路

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

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

class Annotation: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。

class Annotation:  @abstractmethod  def annotationType(self) -> 'Annotation':  raise NotImplementedError

2. 枚举类ElementType

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

class ElementType(Enum):  class Type:  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。

def accepts(*types):  def check_accepts(f):  def wrapper(*args, **kwargs):  if not all(isinstance(arg, types) for arg in args[1:]):  raise TypeError("Argument %s is not of type %s" % (args, types))  return f(*args, **kwargs)  return wrapper  return check_accepts

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

4. (核心)元注解@Target

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

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

4.1 源码

class Target:  @accepts(list, tuple, ElementType)  def __init__(self, elementTypes: Union[List[ElementType], ElementType]):  # 1. 检查列表或者元组类型是否正确  if isinstance(elementTypes, list) or isinstance(elementTypes, tuple):  for e in elementTypes:  if not isinstance(e, ElementType):  raise TypeError(f"@Target只能声明作用范围为ElementType,当前{type(e)}为不支持的类型。")  # 2. 元组和ElementType需要转化为list列表形式  if isinstance(elementTypes, ElementType):  elementTypes = [elementTypes]  self.elementTypes = [e.value for e in elementTypes]  def __call__(self, cls):  class AnnoProxy(cls, Annotation):  def annotationType(self):  return cls  def __init__(self, *args, **kwargs):  if len(args) > 1:  raise TypeError(  f"@{self.__class__.__name__}只能接受一个args参数作为默认value,存在多个参数请使用字典。")  if len(args) == 1:  self.value = args[0] if len(args) > 0 else kwargs.get('value')  super().__init__(*args, **kwargs)  def __new__(_cls, *args, **kwargs):  _instance = super(AnnoProxy, _cls).__new__(_cls)  _instance.source = cls  _instance.elementTypes = self.elementTypes  if len(kwargs) == 0 and len(args) == 1 and (isinstance(args[0], type) or isinstance(args[0],  types.FunctionType)):  return AnnoProxy.wrapper_target(_instance, args[0])  else:  # 其他情况则为 @AriaFor(123)或者@AriaFor(name="Tom")这种带参数的形式调用。  _instance.elementTypes = self.elementTypes  return _instance  def __call__(self, target):  # 如果调用了__call__方法说明目标注解类的使用形式为带参数的类装饰器,即@AriaFor(123)这种  # 此时 target 就是装饰对象,类或者函数  return AnnoProxy.wrapper_target(self, target)  @staticmethod  def wrapper_target(_instance, target):  support_types = [e.value for e in _instance.elementTypes]  labels = [e.label for e in _instance.elementTypes]  if not any(isinstance(target, s_type) for s_type in support_types):  raise TypeError(  f"@{_instance.source.__name__}无法装饰[{type(target).__name__}][{target.__name__}],此注解只能作用在[{'和'.join(labels)}]上")  target.__annotation_list__.append(_instance) if hasattr(target, '__annotation_list__') else setattr(target,  '__annotation_list__',  [_instance])  return target  return AnnoProxy

4.2 说明

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

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

@Target(ElementType.Type)
class AliaFor:pass

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

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

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

使用方式一:

@AliasFor
class A:pass

使用方式二:

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

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

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

效果展示

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

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

1. 不带参数的使用

声明一个注解:

@Target(ElementType.TYPE)  
class AliaFor:  pass

使用注解:

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

输出:

在这里插入图片描述

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

2. 带参数的使用

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

声明一个注解:

@Target(ElementType.TYPE)  
class AliaFor:  def __init__(self, value):  self.value = value

使用注解:

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

输出:

在这里插入图片描述

2.2 注解属性值

声明一个注解:

@Target(ElementType.TYPE)  
class AliaFor:  def __init__(self, name, age):  self.name = name  self.age = age

使用注解:

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

输出:

在这里插入图片描述

3. 错误作用范围异常

声明注解作用在类上:

@Target(ElementType.TYPE)  
class AliaFor:  pass

错误的使用:

class A:  @AliaFor  def add(self):  pass

输出:

在这里插入图片描述

4. 使用工具类

class AnnotationUtils:  @staticmethod  def getAnnotations(source: type) -> Union[List[Annotation], None]:  return source.__annotation_list__ if hasattr(source, '__annotation_list__') else None  @staticmethod  def getAnnotation(source: type, annotation_type: type) -> Union[Annotation, None]:  if AnnotationUtils.getAnnotations(source) is None:  return None  return next((a for a in AnnotationUtils.getAnnotations(source) if isinstance(a, annotation_type)), None)  @staticmethod  def isAnnotationPresent(source: type, annotation_type: type):  return AnnotationUtils.getAnnotation(source, annotation_type) is not None  @staticmethod  def getAnnotationAttributes(annotation: Annotation) -> dict:  return {k: v for k, v in annotation.__dict__.items() if not k.startswith('_')}  @staticmethod  def getAnnotationAttribute(annotation: Annotation, attribute_name: str):  return AnnotationUtils.getAnnotationAttributes(annotation).get(attribute_name, None)

声明一个注解:

@Target([ElementType.TYPE, ElementType.METHOD])  
class AliaFor:  def __init__(self, name, age):  self.name = name  self.age = age

使用注解:

@AliaFor(name="Tom", age=18)  
class A:  @AliaFor  def add(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.xdnf.cn/news/1553114.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Mac 网络连接正常,微信可以使用,但浏览器打不开网页?

解决: Step1,选择🍎图标,选择系统设置(或系统偏好设置)打开; Step2,选择网络,Wi-Fi Step3,选择详细信息; Step4: 选择代理,关闭右…

3.点位管理改造-列表查询——帝可得管理系统

目录 前言一、与页面原型差距1.现在:2.目标:3. 存在问题:所在区域和合作商ID展示的都是ID,而不是名称;同时合作商ID应改为合作商 二、修改1.重新设计SQL语句2.修改mapper层,使用Mybatis中的嵌套查询3.修改s…

C. Tree Pruning【Codeforces Round 975 (Div. 1)】

C. Tree Pruning (永远不知道为什么TLE直到把初始化的memset换成for循环 题意很简单,就是找到一个深度,使得删除最少的节点且所有的叶子节点都为这个深度。 从小到大遍历可能的深度i,容易知道所有 深度大于i的节点 和所有 子树最大深度小于i…

操作符详解与表达式求值

目录 操作符分类 1.算数操作符 2.移位操作符&#xff08;只适用于整数范围&#xff09; &#xff08;1&#xff09;引入 &#xff08;2&#xff09;左移操作符<< &#xff08;2&#xff09;右移操作符>> 3.位操作符 4.赋值操作符 复合赋值符 5.单目操作符 5…

深度优先搜索(DFS)与有向图中的唯一结点

深度优先搜索(DFS)与有向图中的唯一结点 前提与定义分析与方法伪代码与 C 代码实现解释结果在图论中,深度优先搜索(DFS)是一种用于遍历或搜索图的算法。DFS 从给定的起始结点出发,沿着图的深度方向尽可能深地搜索,直到无法继续为止,然后回溯并从未访问过的邻接结点继续…

Unraid的cache使用btrfs或zfs?

Unraid的cache使用btrfs或zfs&#xff1f; 背景&#xff1a;由于在unraid中添加了多个docker和虚拟机&#xff0c;因此会一直访问硬盘。然而&#xff0c;单个硬盘实在难以让人放心。在阵列盘中&#xff0c;可以通过添加校验盘进行数据保护&#xff0c;在cache中无法使用xfs格式…

YOLOv11改进 | Neck篇 | YOLOv11引入Slim-Neck(轻量)

1. Slim-Neck介绍 摘要&#xff1a;目标检测是计算机视觉中重要的下游任务。 对于车载边缘计算平台来说&#xff0c;巨大的模型很难达到实时检测的要求。 而且&#xff0c;由大量深度可分离卷积层构建的轻量级模型无法达到足够的精度。 我们引入了一种新的轻量级卷积技术 GSCon…

【顺序查找】

目录 一. 顺序查找的概念二. 查找的性能计算 \quad 一. 顺序查找的概念 \quad \quad 二. 查找的性能计算 \quad

使用ROCm的GPU感知MPI

GPU-aware MPI with ROCm — ROCm Blogs (amd.com) 注意: 此博客之前是 AMD Lab Notes博客系列的一部分。 MPI&#xff08;消息传递接口&#xff09;是高性能计算中进程间通信的事实标准。MPI进程在其本地数据上进行计算&#xff0c;同时进行大量的相互通信。这使得MPI程序可以…

【折半查找】

目录 一. 折半查找的概念二. 折半查找的过程三. 折半查找的代码实现四. 折半查找的性能分析 \quad 一. 折半查找的概念 \quad 必须有序 \quad 二. 折半查找的过程 \quad \quad 三. 折半查找的代码实现 \quad 背下来 \quad 四. 折半查找的性能分析 \quad 记住 比较的是层数 …

sed引入变量中的坑

sed引入变量问题 1、sed引入变量2、sed引入变量问题 1、sed引入变量 sed指令引入变量&#xff0c;直接使用双引号即可 例如&#xff0c;下面的示例&#xff1a; ab; echo "abc" | sed "s/b/$a/g"2、sed引入变量问题 但是&#xff0c;如果变量值中带有/等…

自闭症寄宿学校:释放孩子内心的美

在自闭症儿童的成长旅程中&#xff0c;寻找一个既能提供专业康复服务&#xff0c;又能让孩子感受到爱与关怀的教育环境&#xff0c;是许多家庭梦寐以求的目标。在广州&#xff0c;星贝育园自闭症儿童寄宿制学校正是这样一所充满爱与希望的学校&#xff0c;它不仅为自闭症儿童提…

CMU 10423 Generative AI:lec13/13.5(text-to-image models:三大类方法、评估标准、图像编辑原理)

1 文章目录 1 lec13和lec13.5概述2 Text-to-Image Generation 概念、主要方法、挑战、发展历程1. **基本概念**2. **主要技术方法**2.1. **生成对抗网络&#xff08;GAN&#xff09;**2.2. **自回归模型&#xff08;Autoregressive Models&#xff09;**2.3. **扩散模型&#x…

9.28学习笔记

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看&#xff1a; 使用transformer作为其主干网络&#xff0c;代替了原先的UNet 在latent space进行训练&#xff0c;通过transformer处理潜…

Java项目实战II基于Java+Spring Boot+MySQL的智能物流管理系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 随着电商行业的蓬勃发展&#xff0c;物流行业迎来了前所未有的机遇与挑战。面对日益增长的订单量和复…

python如何显示数组

np.set_printoptions方法的相关属性&#xff1a; <span style"background-color:#272822"><span style"color:#f8f8d4">set_printoptions(precisionNone, thresholdNone, edgeitemsNone, linewidthNone, suppressNone, nanstrNone, infstrNo…

记一次RCE漏洞的利用

某微商代理商补货商城系统存在RCE漏洞 微商分销代理商城&#xff0c;可以自己设置代理等级和升级条件(如购买指定商品、消费额度)&#xff0c;“微商城小程序三级分销拼团秒杀多商户开店O2O门店”通过社交关系分销裂变&#xff0c;把粉丝变成客户&#xff0c;让分销商发展下线…

MDM监管锁系统ABM证书与MDM证书申请与使用

MDM证书与ABM证书申请与维护 基础知识 监管锁系统运行需要两个证书 分别为ABM证书 与 MDM证书,在别人平台购买的监管锁只会让你上传自己的ABM证书而MDM证书则是共用一个平台自己的MDM证书&#xff0c;而MDM证书才是控制手机的关键,如果MDM证书被封禁,那么所有的设备将无法受到…

MDM监管锁系统上锁流程

上锁与解锁 上锁设备 完整的上锁流程可参考: https://b23.tv/UvM35sU 上锁需要已经注册了一个普通用户 并使用管理员分配了台数 且有可用的MDM证书和ABM证书(公有和私有的都可以 只要有可用的就可以) 一部用来上锁的手机 链接wifi wifi必须要是2.4g频段 不要使用5gwifi 上锁…

PYTHON实现HTTP request的一些有用的函数

前言 我们知道&#xff0c;当需要设计一个程序和服务器进行交互时&#xff0c;往往会用到HTTP的request&#xff0c;即服务器有一个对外接口REST API&#xff0c;因此当向服务器发送符合格式要求的HTTP request时&#xff0c;服务器会给出响应&#xff0c;甚至执行一些任务。如…