基于 Validator 类实现 ParamValidator,用于校验函数参数

目录

  • 一、前置说明
    • 1、总体目录
    • 2、相关回顾
    • 3、本节目标
  • 二、操作步骤
    • 1、项目目录
    • 2、代码实现
    • 3、测试代码
    • 4、日志输出
  • 三、后置说明
    • 1、要点小结
    • 2、下节准备

一、前置说明

1、总体目录

  • 《 pyparamvalidate 参数校验器,从编码到发布全过程》

2、相关回顾

  • 使用 TypeVar 创建 Self 类型变量,方便用户在 Pycharm 编辑器中链式调用校验方法

3、本节目标

  • 了解 __getattr__ 的特性。
  • 了解 __call__ 的用法。
  • 了解如何在一个类中动态的使用另一个类中的方法。

二、操作步骤

1、项目目录

  • atme : @me 用于存放临时的代码片断或其它内容。
  • pyparamvalidate : 新建一个与项目名称同名的package,为了方便发布至 pypi
  • core : 用于存放核心代码。
  • tests : 用于存放测试代码。
  • utils : 用于存放一些工具类或方法。

2、代码实现

atme/demo/validator_v5/validator.py


import functools
import inspect
from typing import TypeVardef _error_prompt(value, exception_msg=None, rule_des=None, field=None):default = f'"{value}" is invalid.'prompt = exception_msg or rule_desprompt = f'{default} due to: {prompt}' if prompt else defaultprompt = f'{field} error: {prompt}' if field else promptreturn promptdef raise_exception(func):@functools.wraps(func)def wrapper(self, *args, **kwargs):bound_args = inspect.signature(func).bind(self, *args, **kwargs).argumentsexception_msg = kwargs.get('exception_msg', None) or bound_args.get('exception_msg', None)error_prompt = _error_prompt(self.value, exception_msg, self._rule_des, self._field)result = func(self, *args, **kwargs)if not result:raise ValueError(error_prompt)return selfreturn wrapperclass RaiseExceptionMeta(type):def __new__(cls, name, bases, dct):for key, value in dct.items():if isinstance(value, staticmethod):dct[key] = staticmethod(raise_exception(value.__func__))if isinstance(value, classmethod):dct[key] = classmethod(raise_exception(value.__func__))if inspect.isfunction(value) and not key.startswith("__"):dct[key] = raise_exception(value)return super().__new__(cls, name, bases, dct)'''
- TypeVar 是 Python 中用于声明类型变量的工具
- 声明一个类型变量,命名为 'Self', 意思为表示类的实例类型
- bound 参数指定泛型类型变量的上界,即限制 'Self' 必须是 'Validator' 类型或其子类型
'''
Self = TypeVar('Self', bound='Validator')class Validator(metaclass=RaiseExceptionMeta):def __init__(self, value, field=None, rule_des=None):self.value = valueself._field = fieldself._rule_des = rule_desdef is_string(self, exception_msg=None) -> Self:"""将返回类型注解定义为 Self, 支持编辑器如 pycharm 智能提示链式调用方法,如:Validator(input).is_string().is_not_empty()- 从 Python 3.5 版本开始支持类型注解- 在 Python 3.5 中引入了 PEP 484(Python Enhancement Proposal 484),其中包括了类型注解的概念,并引入了 typing 模块,用于支持类型提示和静态类型检查;- 类型注解允许开发者在函数参数、返回值和变量上添加类型信息,但是在运行时,Python 解释器不会检查这些注解是否正确;- 它们主要用于提供给静态类型检查器或代码编辑器进行,以提供更好的代码提示和错误检测;- Python 运行时并不强制执行这些注解,Python 依然是一门动态类型的语言。- 本方法中:- 返回值类型为 bool 类型,用于与装饰器函数 raise_exception 配合使用,校验 self.value 是否通过;- 为了支持编辑器如 pycharm 智能识别链式调用方法,将返回类型注解定义为 Self, 如:Validator(input).is_string().is_not_empty();- Self, 即 'Validator', 由 Self = TypeVar('Self', bound='Validator') 定义;- 如果返回类型不为 Self, 编辑器如 pycharm 在 Validator(input).is_string() 之后,不会智能提示 is_not_empty()"""return isinstance(self.value, str)def is_not_empty(self, exception_msg=None) -> Self:return bool(self.value)

atme/demo/validator_v5/param_validator.py


import inspect
from functools import wraps
from typing import Callablefrom atme.demo.validator_v5.validator import Validatorclass ParameterValidator:def __init__(self, param_name: str, param_rule_des=None):""":param param_name: 参数名:param param_rule_des: 该参数的规则描述"""self.param_name = param_nameself.param_rule_des = param_rule_desself._validators = []def __getattr__(self, name: str):"""当调用一个不存在的属性或方法时,Python 会自动调用 __getattr__ 方法,因此可以利用这个特性,动态收集用户调用的校验方法。以用户使用 ParamValidator("param").is_string(exception_msg='param must be string').is_not_empty() 为例,代码执行过程如下:1. 当用户调用 ParamValidator("param").is_string(exception_msg='param must be string') 时,2. 由于 is_string 方法不存在,__getattr__ 方法被调用,返回 validator_method 函数(此时未被调用),is_string 方法实际上是 validator_method 函数的引用,3. 当执行 is_string(exception_msg='param must be string') 时,is_string 方法被调用, 使用关键字参数传递 exception_msg='param must be string',4. 实际上是执行了 validator_method(exception_msg='param must be string') , validator_method 函数完成调用后,执行函数体中的逻辑:- 向 self._validators 中添加了一个元组 ('is_string', (),  {'exception_msg': 'param  must  be  string'})- 返回 self 对象5. self 对象继续调用 is_not_empty(), 形成链式调用效果,此时的 validator_method 函数的引用就是 is_not_empty, 调用过程与 1-4 相同。"""def validator_method(*args, **kwargs):self._validators.append((name, args, kwargs))return selfreturn validator_methoddef __call__(self, func: Callable) -> Callable:"""使用 __call__ 方法, 让 ParameterValidator 的实例变成可调用对象,使其可以像函数一样被调用。'''@ParameterValidator("param").is_string()def example_function(param):return paramexample_function(param="test")'''以这段代码为例,代码执行过程如下:1. 使用 @ParameterValidator("param").is_string() 装饰函数 example_function,相当于: @ParameterValidator("param").is_string()(example_function)2. 此时返回一个 wrapper 函数(此时未调用), example_function 函数实际上是 wrapper 函数的引用;3. 当执行 example_function(param="test") 时,相当于执行 wrapper(param="test"), wrapper 函数被调用,开始执行 wrapper 内部逻辑, 见代码中注释。"""@wraps(func)def wrapper(*args, **kwargs):# 获取函数的参数和参数值bound_args = inspect.signature(func).bind(*args, **kwargs).argumentsif self.param_name in kwargs:# 如果用户以关键字参数传值,如 example_function(param="test") ,则从 kwargs 中取参数值;value = kwargs[self.param_name]else:# 如果用户以位置参数传值,如 example_function("test"),则从 bound_args 是取参数值;value = bound_args.get(self.param_name)# 实例化 Validator 对象validator = Validator(value, field=self.param_name, rule_des=self.param_rule_des)# 遍历所有校验器(注意:这里使用 vargs, vkwargs,避免覆盖原函数的 args, kwargs)for method_name, vargs, vkwargs in self._validators:# 通过 函数名 反射获取校验函数对象validate_method = getattr(validator, method_name)# 执行校验函数validate_method(*vargs, **vkwargs)# 执行原函数return func(*args, **kwargs)return wrapper

3、测试代码

atme/demo/validator_v5/test_param_validator.py


import pytestfrom atme.demo.validator_v5.param_validator import ParameterValidatordef test_is_string_validator_passing_01():"""校验一个参数"""@ParameterValidator("param").is_string(exception_msg='param must be string')def example_function(param):print(param)return paramassert example_function(param="test") == "test"with pytest.raises(ValueError) as exc_info:example_function(param=123)print(exc_info.value)assert "invalid" in str(exc_info.value)def test_is_string_validator_passing_02():"""校验多个参数"""@ParameterValidator("param2").is_string().is_not_empty()@ParameterValidator("param1").is_string().is_not_empty()def example_function(param1, param2):print(param1, param2)return param1, param2assert example_function("test1", "test2") == ("test1", "test2")with pytest.raises(ValueError) as exc_info:example_function(123, 123)print(exc_info.value)assert "invalid" in str(exc_info.value)

4、日志输出

执行 test 的日志如下,验证通过:

============================= test session starts =============================
collecting ... collected 2 itemstest_param_validator.py::test_is_string_validator_passing_01 PASSED      [ 50%]test
param error: "123" is invalid. due to: param must be stringtest_param_validator.py::test_is_string_validator_passing_02 PASSED      [100%]test1 test2
param2 error: "123" is invalid.============================== 2 passed in 0.01s ==============================

三、后置说明

1、要点小结

  • 当调用一个不存在的属性或方法时,Python 会自动调用 __getattr__ 方法,可以利用这个特性,动态收集用户调用的校验方法。
  • 使用 __call__ 方法, 让 ParameterValidator 的实例变成可调用对象,使其可以像函数一样被调用。
  • 可以结合使用 __getattr____call__ 方法,实现在一个类中动态调用另一个类中的方法。
  • 虽然从功能上实现了校验函数参数的功能,但由于 ParameterValidator 并没有显式的定义 is_string()is_not_empty() 方法,编辑器无法智能提示可校验方法,需要进一步优化。

2、下节准备

  • 优化 ParamValidator,让编辑器 Pycharm 智能提示校验方法

点击返回主目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/334109.html

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

相关文章

龙蜥副理事长张东:潮蜥共引,繁荣系统软件生态 | 2023龙蜥操作系统大会

2023 年 12 月 17-18 日,由开放原子开源基金会指导,龙蜥社区主办,阿里云、中兴通讯、浪潮信息、Arm、Intel 等 24 家理事单位共同承办,主题为“云智融合共筑未来”的 2023 龙蜥操作系统大会在北京圆满结束。本次大会上&#xff0c…

2024第十六届数字图像处理国际会议(ICDIP 2024) 即将召开!

第十六届数字图像处理国际会议(ICDIP 2024)将于2024年5月24-26日在中国海口召开。本次会议由海南大学主办,海南大学计算机科学与技术学院承办。ICDIP自2009年开办以来,已先后在新加坡、日本、中国、马来西亚理科大学等国家成功举办…

记录汇川:ITP与Autoshop进行仿真连接

1、定义如下程序: 2、ITP新建工程: 3、依次选择,最后修改IP 4、定义两个变量 5、拖一个按钮和一个圈出来,地址绑定:M1 6、地址绑定:Y1 7、PLC启动仿真 8、ITP启动在线模拟器 9、即可实现模拟仿真

高效构建Java应用:Maven入门和进阶(三)

高效构建Java应用:Maven入门和进阶(三) 三. Maven的核心功能和构建管理3.1 依赖管理和配置3.2 依赖传递和冲突3.3 依赖导入失败场景和解决方案3.4 扩展构建管理和插件配置 三. Maven的核心功能和构建管理 3.1 依赖管理和配置 Maven 依赖管理…

奇偶链表00

题目链接 奇偶链表 题目描述 注意点 在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题偶数组和奇数组内部的相对顺序应该与输入时保持一致 解答思路 奇数组的头节点是head,偶数组的头节点是head.next,关键是要改变每个节点的next指针及…

Linux习题2

解析:虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存,使得每个进程看到的内存空间一致。 解析:所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的&…

工程项目管理系统源码与Spring Cloud:实现高效系统管理与二次开发

随着企业规模的不断扩大和业务的快速发展,传统的工程项目管理方式已经无法满足现代企业的需求。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性,企业需要借助先进的数字化技术进行转型。本文将介绍一款采用Spring CloudSpring BootMybat…

为什么企业上了ERP,效率反而更低?

“为什么上了ERP,效率反而更低?” 不知道这种疑惑是在什么情况下产生的? 毕竟一句“上了ERP,效率反而变低了”并不能概括所有可能的情况—— 是某个车间效率变低了,某条生产线效率变低了?还是企业整体效…

Java 求2个整数,3个整数 的 10等分比例值

10等份取整比 比如 1.5 : 4 : 4.5 会变成 1:4:5 ,当然小数后一位的四舍五入是向上还是向下去整,这个根据自己需要调整即可。 代码 : public static Integer getIntTenPerNum(Integer nu…

LeetCode 每日一题 Day 3334(hard)35 ||二进制枚举/单调栈/链表遍历

2397. 被列覆盖的最多行数 给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix ;另给你一个整数 numSelect,表示你必须从 matrix 中选择的 不同 列的数量。 如果一行中所有的 1 都被你选中的列所覆盖,则认为这一行被 覆盖 了。 形式…

【深度学习:SimCLR 】利用 SimCLR 推进自监督和半监督学习

【深度学习:SimCLR 】利用 SimCLR 推进自监督和半监督学习 SimCLR框架表现代码和预训练模型 最近,自然语言处理模型,如BERT和T5,已经表明,通过首先在大型未标记数据集上进行预训练,然后在较小的标记数据集上…

第三节课作业

复现: 对mmagic的理解