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.rhkb.cn/news/437201.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

828华为云征文|华为云弹性云服务器FlexusX实例下的Nginx性能测试

本文写的是华为云弹性云服务器FlexusX实例下的Nginx性能测试 目录 一、华为云弹性云服务器FlexusX实例简介二、测试环境三、测试工具四、测试方法五、测试结果 下面是华为云弹性云服务器FlexusX实例下的Nginx性能测试。 一、华为云弹性云服务器FlexusX实例简介 华为云弹性云服…

【LLM论文日更】| 通过指令调整进行零样本稠密检索的无监督文本表示学习

论文:https://arxiv.org/pdf/2409.16497代码:暂未开源机构:Amazon AGI、宾夕法尼亚州立大学领域:Dense Retrieval发表:Accepted at DCAI24 workshopCIKM2024 研究背景 研究问题:这篇文章要解决的问题是如…

泰勒图 ——基于相关性与标准差的多模型评价指标可视化比较-XGBoost、sklearn

1、基于相关性与标准差的多模型评价指标可视化比较 # 数据读取并分割 import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split plt.rcParams[font.family] = Times New Roman plt.rcParams[axes.unic…

【C++】第一节:C++入门

1、C关键字 2、命名空间 在C/C中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染&am…

Updates were rejected because the tip of your current branch is behind 的解决方法

1. 问题描述 当我们使用 git push 推送代码出现以下问题时: 2. 原因分析 这个错误提示表明当前本地分支落后于远程分支,因此需要先拉取远程的更改。 3. 解决方法 1、拉取远程更改 在终端中执行以下命令,拉取远程分支的更新并合并到本地…

奔驰EQS450suv升级增强AR抬头显示HUD案例分享

以下是奔驰 EQS450 SUV 升级增强版 AR 抬头显示的一般改装案例步骤及相关信息: 配件:通常包括显示屏、仪表模块、饰板等。 安装步骤: 1. 拆下中控的仪表。 2. 在仪表上预留位置切割出合适的孔位,用于安装显示器。 3. 将显示器…

【JavaEE】——多线程常用类

阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 引入: 一:Callable和FutureTask类 1:对比Runnable 2&#xff1a…

动手学深度学习-GPU常见报错-CUDA11.4-AssertionError: Torch not compiled with CUDA enabled

目录 本文还能解决: 0. 问题原因 1. 查看机器的cuda版本 2. 从官网下载对应的torch和torchvision 3. 具体安装方法 本文还能解决: torch.cuda.is_available() 输出为 False; torch.cuda.device_count() 输出为 0 0. 问题原因 这两个问题…

【C++笔记】初始模版和STL简介

【C笔记】初始模版和STL简介 🔥个人主页:大白的编程日记 🔥专栏:C笔记 文章目录 【C笔记】初始模版和STL简介前言一.初始模版1.1泛型编程1.2函数模版1.3类模板 二.STL简介2.1什么是STL2.2STL的版本2.3STL的六大组件2.4STL的重要…

【C++并发入门】opencv摄像头帧率计算和多线程相机读取(下):完整代码实现

前言 高帧率摄像头往往应用在很多opencv项目中,今天就来通过简单计算摄像头帧率,抛出一个单线程读取摄像头会遇到的问题,同时提出一种解决方案,使用多线程对摄像头进行读取。上一期:【C并发入门】摄像头帧率计算和多线…

RDI ADCP命令与ASCII输出结构

RDI ADCP命令与ASCII输出结构 一、RDI垂直式ADCP:1.1固定命令:1.2 向导命令 二、RDI水平式ADCP三、ADCP 公共目录四、常用BBTalk命令五、ADCP的ASCII输出数据文件、流量与数据结构5.1 ASCII类输出:5.2 ASCII 输出数据文件头5.3 ASCII 输出数据集5.4 导航…

Llama 3.2来了,多模态且开源!AR眼镜黄仁勋首批体验,Quest 3S头显价格低到离谱

如果说 OpenAI 的 ChatGPT 拉开了「百模大战」的序幕,那 Meta 的 Ray-Ban Meta 智能眼镜无疑是触发「百镜大战」的导火索。自去年 9 月在 Meta Connect 2023 开发者大会上首次亮相,短短数月,Ray-Ban Meta 就突破百万销量,不仅让马…

位运算(6)_只出现一次的数字 II

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 位运算(6)_只出现一次的数字 II 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 …

psutil库的使用说明

前言 psutil是一个跨平台的库,用于获取系统的进程和系统利用率(包括 CPU、内存、磁盘、网络等)信息。 目录 安装 应用场景 常用方法 一、系统信息相关函数 二、进程信息相关函数 三、网络信息相关函数 四、其他实用函数 使用样例 监控应…

Could not find com.mapbox.mapboxsdk:mapbox-android-accounts:0.7.0.解决

AndroidStudio编译APK出现如下错误: Could not find com.mapbox.mapboxsdk:mapbox-android-accounts:0.7.0. 出现上面错误原因是因为没有打开对应的仓库导致的, 手动添加如下创建地址可解决: maven { url https://maven.aliyun.com/repos…

Windows远程Kylin系统-xrdp

Windows远程Kylin系统-xrdp 一. 查看开放端口 查看是否有3389端口二. 安装xrdp Kylin对应的是centos8 下载链接:https://rhel.pkgs.org/8/epel-x86_64/xrdp-0.10.1-1.el8.x86_64.rpm.html rpm -Uvh 包名 systemctl start xrdp 启动服务 systemctl enable xrdp …

【HTML5】html5开篇基础(4)

1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的…

解决问题AttributeError: “safe_load“ has been removed, use

解决问题AttributeError: "safe_load" has been removed, use~ 1. 问题描述2. 解决方法 1. 问题描述 在复现cdvae代码时,运行 python scripts/compute_metrics.py --root_path MODEL_PATH --tasks recon gen opt评估模型时,出现以下问题。 …

Python批量下载PPT模块并实现自动解压

日常工作中,我们总是找不到合适的PPT模板而烦恼。即使有免费的网站可以下载,但是一个一个地去下载,然后再批量解压进行查看也非常的麻烦,有没有更好方法呢? 今天,我们利用Python来爬取一个网站上的PPT&…

【ios】---swift开发从入门到放弃

swift开发从入门到放弃 环境swift入门变量与常量类型安全和类型推断print函数字符串整数双精度布尔运算符数组集合set字典区间元祖可选类型循环语句条件语句switch语句函数枚举类型闭包数组方法结构体 环境 1.在App Store下载Xcode 2.新建项目(可以先使用这个&…