目录
- 一、前置说明
- 1、总体目录
- 2、相关回顾
- 3、本节目标
- 二、操作步骤
- 1、项目目录
- 2、代码实现
- 3、测试代码
- 4、日志输出
- 三、后置说明
- 1、要点小结
- 2、下节准备
一、前置说明
1、总体目录
- 《 pyparamvalidate 参数校验器,从编码到发布全过程》
2、相关回顾
- 使用 raise_exception 装饰器,优化校验方法
3、本节目标
- 了解元类
metaclass
的使用。 - 了解
__new__
方法的使用。 - 了解如何使用元类隐式装饰类中的所有方法。
二、操作步骤
1、项目目录
atme
:@me
用于存放临时的代码片断或其它内容。pyparamvalidate
: 新建一个与项目名称同名的package,为了方便发布至pypi
。core
: 用于存放核心代码。tests
: 用于存放测试代码。utils
: 用于存放一些工具类或方法。
2、代码实现
atme/demo/validator_v3/validator.py
import functools
import inspectdef _error_prompt(value, exception_msg=None, rule_des=None, field=None):"""优先使用校验方法中的错误提示, 如果方法中没有错误提示,则使用"字段规则描述"代替错误提示拼接出:name error: "123" is invalid. due to: name must be string."""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():# 如果是静态方法,则将它替换为一个新的静态方法,新的静态方法调用 raise_exception 函数,将原静态方法作为参数传递给raise_exceptionif isinstance(value, staticmethod):dct[key] = staticmethod(raise_exception(value.__func__))# 如果是类方法,则将它替换为一个新的类方法,新的类方法调用 raise_exception 函数,将原类方法作为参数传递给raise_exceptionif isinstance(value, classmethod):dct[key] = classmethod(raise_exception(value.__func__))# 如果是普通的成员方法,则将它替换为一个新的函数,新函数调用 raise_exception 函数,将原函数作为参数传递给 raise_exception# 排除掉以双下划线 __ 开头的方法, 如 __init__,__new__等if inspect.isfunction(value) and not key.startswith("__"):dct[key] = raise_exception(value)return super().__new__(cls, name, bases, dct)class Validator(metaclass=RaiseExceptionMeta):def __init__(self, value, field=None, rule_des=None):""":param value: 待校验的值:param field: 校验字段- 用于提示具体哪个字段错误- 如 'name error: name must be string'- error 前面的 `name` 即为 field:param rule_des: 校验规则描述"""self.value = valueself._field = fieldself._rule_des = rule_desdef is_string(self, exception_msg=None):return isinstance(self.value, str)def is_not_empty(self, exception_msg=None):return bool(self.value)
3、测试代码
atme/demo/validator_v3/test_validator.py
import pytestfrom atme.demo.validator_v3.validator import Validatordef test_validator_01():"""在 Validator 实例化时,不给 field、rule_des 传值; 在校验方法中,不给 exception_msg 传值"""validator = Validator('Jane')assert validator.is_string().is_not_empty()with pytest.raises(ValueError) as exc_info:validator = Validator(123)validator.is_string().is_not_empty()assert 'invalid' in str(exc_info.value)print(exc_info.value) # 输出: "123" is invalid.def test_validator_02():"""在 Validator 实例化时,给 field、rule_des 传值"""validator = Validator('Jane', field='name', rule_des='name must be string from rule des.')assert validator.is_string().is_not_empty()with pytest.raises(ValueError) as exc_info:validator = Validator(123, field='name', rule_des='name must be string from rule des.')validator.is_string().is_not_empty()assert 'name must be string from rule des.' in str(exc_info.value)print(exc_info.value) # 输出: name error: "123" is invalid. due to: name must be string from rule des.def test_validator_03():"""在 Validator 实例化时,给 field、rule_des 传值; 在校验方法中,给 exception_msg 传值"""validator = Validator('Jane', field='name', rule_des='name must be string from rule des.')assert validator.is_string().is_not_empty()with pytest.raises(ValueError) as exc_info:validator = Validator(123, field='name', rule_des='name must be string from rule des.')validator.is_string('name must be string from method exception msg.').is_not_empty()assert 'name must be string from method exception msg.' in str(exc_info.value)print(exc_info.value) # 输出: "123" is invalid due to "name error: name must be string from method exception msg."def test_validator_04():"""field_name 为空"""validator = Validator('Jane', rule_des='name must be string from rule des.')assert validator.is_string().is_not_empty()with pytest.raises(ValueError) as exc_info:validator = Validator(123, rule_des='name must be string from rule des.')validator.is_string('name must be string from method exception msg.').is_not_empty()assert 'name must be string from method exception msg.' in str(exc_info.value)print(exc_info.value) # 输出: "123" is invalid due to "name must be string from method exception msg."
4、日志输出
执行 test
的日志如下,验证通过:
============================= test session starts =============================
collecting ... collected 4 itemstest_validator.py::test_validator_01 PASSED [ 25%]"123" is invalid.test_validator.py::test_validator_02 PASSED [ 50%]name error: "123" is invalid. due to: name must be string from rule des.test_validator.py::test_validator_03 PASSED [ 75%]name error: "123" is invalid. due to: name must be string from method exception msg.test_validator.py::test_validator_04 PASSED [100%]"123" is invalid. due to: name must be string from method exception msg.============================== 4 passed in 0.01s ==============================
三、后置说明
1、要点小结
- 元类 metaclass 可以控制类的创建过程,可以动态的修改类的属性、方法和其他行为,demo 示例如下:
class MyMeta(type):def __new__(cls, name, bases, dct):# 在创建类之前的操作print(f"Creating class: {name}")return super().__new__(cls, name, bases, dct)class MyClass(metaclass=MyMeta):pass# 输出:Creating class: MyClass
__new__
方法用于修改类的行为,在RaiseExceptionMeta
类中,它会遍历类的字典(包括属性和方法),并对其中的静态方法、类方法和普通成员方法进行修改。__new__
是在对象实例创建之前调用的方法,用于创建并返回一个新的实例;__init__
是实例创建之后调用的方法,用于对实例进行初始化。- 经过优化后,由于校验方法没有
return self
, 导致编辑器如pycharm
不能智能识别可用的链式调用方法(如下图),对用户很不友好,可以继续优化。
2、下节准备
- 使用
TypeVar
创建Self
类变量,方便用户在pycharm
编辑器中进行链式调用
点击返回主目录