基于 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.mzph.cn/news/611585.shtml

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

相关文章

Every Nobody Is Somebody 「每小人物都能成大事」

周星驰 NFT Nobody即将发售,Nobody共创平台 Every Nobody Is Somebody Nobody 关于Nobody:Nobody是一款Web3共创平台,旨在为创作者提供一个交流和合作的场所,促进创意的产生和共享。通过该平台,创作者可以展示自己的作…

git秘钥过期 ERROR: Your SSH key has expired

文章目录 1、错误提示Your SSH key has expired2、登录Github确认3、重新设置秘钥 1、错误提示Your SSH key has expired 使用git命令时遇到Github 的 SSH Key秘钥过期,提示错误ERROR: Your SSH key has expired 2、登录Github确认 首先登录Github查看&#xff…

L1-015 跟奥巴马一起画方块(Java)

美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在…

某查查请求头参数加密分析(含JS加密算法与Python爬虫源码)

文章目录 1. 写在前面2. 请求分析3. 断点分析4. 扣加密JS5. Python爬虫代码实现 【作者主页】:吴秋霖 【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作! 【作者推荐】&#xff…

基于SELinux三权分立配置方法

1.系统安装 系统安装完成后,系统当前的SELinux配置为: # cat /etc/selinux/config SELINUX=enforcing SELINUXTYPE=targeted 2.SELinux环境准备 # yum install setools policycoreutils.x86_64 selinux-policy-mls.noarch setroubleshoot.x86_64 setools-console -y 3.SELin…

嵌入式c语言学习笔记:可重入函数与不可重入函数

什么是可重入函数与不可重入函数? 在一个多任务环境中,一个函数如果可以被多次重复调用,或者被多个任务并发调用,函数在运行过程中可以随时随地被打断,并不影响该函数的运行结果,我们称这样的函数为可重入…

Redis 常用的数据类型及用法

1. 字符串(Strings) 字符串是最基本的 Redis 数据类型。它可以包含任何形式的数据,比如文本、数字或二进制数据。 基本用法: 设置值: SET key value获取值: GET key删除键: DEL key自增: INCR key追加值: APPEND key value 示…

vue element plus TimePicker 时间选择器

用于选择或输入日期 TIP 在 SSR 场景下&#xff0c;您需要将组件包裹在 <client-only></client-only> 之中 (如: Nuxt) 和 SSG (e.g: VitePress). 任意时间点# 可以选择任意时间 提供了两种交互方式&#xff1a;默认情况下通过鼠标滚轮进行选择&#xff0c;打开…

三、java线性表(顺序表、链表、栈、队列)

java线性表 三、线性表1.1 顺序表1.2 链表1.2.1 单向链表&#xff08;Singly Linked List&#xff09;1.2.2 双向链表&#xff08;Doubly Linked List&#xff09; 1.3 LinkedList VS ArrayList1.3.7 使用 LinkedList 的场景 1.4 栈1.5 队列 三、线性表 线性表是一种经典的数据…

LeetCode2696. Minimum String Length After Removing Substrings

文章目录 一、题目二、题解 一、题目 You are given a string s consisting only of uppercase English letters. You can apply some operations to this string where, in one operation, you can remove any occurrence of one of the substrings “AB” or “CD” from s…

手撕单链表(单向,不循环,不带头结点)的基本操作

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

mercury靶机

文章妙语 不与伪君子争名&#xff0c;不与真小人争利&#xff0c;不与执拗人争理&#xff0c;不与匹夫争勇&#xff0c;不与酸儒争才。不与蠢人施恩 一、信息收集 主机探测 端口探测 探测主机详细版本信息 8080开了http服务 目录扫描 robots.txt目录下什么也没有 二&#xff0…

Python | Iter/genartor | 一文了解迭代器、生成器的含义\区别\优缺点

前提 一种技术的出现&#xff0c;需要考虑&#xff1a; 为了实现什么样的需求&#xff1b;遇到了什么样的问题&#xff1b;采用了什么样的方案&#xff1b;最终接近或达到了预期的效果。 概念 提前理解几个概念&#xff1a; 迭代 我们经常听到产品迭代、技术迭代、功能迭代…

oracle常用内部表和视图

Oracle数据库中存在大量内部表和视图&#xff0c;主要用于系统管理和维护。这里列出一些常用的内部视图&#xff08;数据字典视图&#xff09;和内部表&#xff1a; **常用内部视图&#xff08;Data Dictionary Views&#xff09;&#xff1a;** 1. **用户权限相关视图&#…

零基础学习数学建模——(二)数学建模的步骤

本篇博客将详细介绍数学建模的步骤。 文章目录 引例&#xff1a;年夜饭的准备第一步&#xff1a;模型准备第二步&#xff1a;模型假设第三步&#xff1a;模型建立第四步&#xff1a;模型求解第五步&#xff1a;结果分析第六步&#xff1a;模型检验第七步&#xff1a;模型应用及…

爬虫案例—抓取小米商店应用

爬虫案例—抓取小米商店应用 代码如下&#xff1a; # 抓取第一页的内容 import requests from lxml import etree url ‘https://app.mi.com/catTopList/0?page1’ headers { ‘User-Agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (K…

go中for range的坑以及解决方案

一、for range的坑 相信小伙伴都遇到过以下的循环变量的问题&#xff0c;那是因为循环的val变量是重复使用的&#xff0c;即仅有一份。也就是说&#xff0c;每次循环后赋给val的值就会把前面循环赋给val的值替换掉&#xff0c;所以打印出来的值都是最后一次循环赋给val的值。 …

openeuler的安装和两台linux主机配置ssh实现互相免密登陆

一、openeuler的安装 下载OpenEuler - 网址&#xff1a;https://www.openeuler.org/zh/download/archive/ - 版本选择&#xff1a;openEuler 22.03 LTS SP2 &#xff08;镜像文件&#xff09; &#xff0c;即长期更新版 设置自定义硬件 内存&#xff1a;推荐2GB 处理器&…

oracle基本用户管理和权限分配

1.用户与模式的关系&#xff0c;一一对应的关系 2.创建与管理用户 2.1创建用户语法 CREATE user wdf IDENTIFIED by tiger--创建用户wdf,密码tiger DEFAULT tablespace users--用户的默认表空间 quota 10M on users;--在表空间的占用最大空间 注意&#xff1a;用户创建以后…

rke2 Offline Deploy Rancher v2.8.0 latest (helm 离线部署 rancher v2.8.0)

文章目录 预备条件为什么是三个节点&#xff1f;​预备条件配置私有仓库介质清单安装 helm安装 cert-manager下载介质镜像入库helm 部署卸载 安装 rancher镜像入库helm 安装 验证 预备条件 所有支持的操作系统都使用 64-bit x86 架构。Rancher 兼容当前所有的主流 Linux 发行版…