使用 raise_exception 装饰器,简化 if not ... raise ... 抛出异常的过程

简介: 使用 raise_exception 装饰器,简化 if not ... raise ... 抛出异常的过程

一、前置说明


1、本节目标

  • 了解装饰器的基本原理和使用。
  • 使用 raise_exception 装饰器,简化 if not ... raise ... 抛出异常的过程。


2、相关回顾


二、操作步骤


1、项目目录


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


2、代码实现

atme/demo/validator_v2/validator.py

import functools
import inspect
def _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_des
    prompt = f'{default} due to: {prompt}' if prompt else default
    prompt = f'{field} error: {prompt}' if field else prompt
    return prompt
# python 装饰器基础, 请参考:https://kindtester.blog.csdn.net/article/details/135550880
def raise_exception(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        # 将 "参数名" 和 "参数值" 绑定为键值对
        bound_args = inspect.signature(func).bind(self, *args, **kwargs).arguments
        # 如果 参数名 在 kwargs 中,则优先从 kwargs 中获取,对应: 以关键字参数进行传值的情形, 如 is_string('Jane', exception_msg='name must be string')
        # 否则,从 bound_args 中获取,对应: 以位置参数进行传值的情形,如 is_string('Jane', 'name must be string')
        exception_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 self
    return wrapper
class Validator:
    def __init__(self, value, field=None, rule_des=None):
        self.value = value
        self._field = field
        self._rule_des = rule_des
    @raise_exception
    def is_string(self, exception_msg=None):
        """
        exception_msg 会在 raise_exception 装饰器中隐式调用。
        """
        return isinstance(self.value, str)
    '''
    @raise_exception
    def is_string(self, **kwargs):
        """
        虽然这种方式能实现同样的效果,但是不推荐使用,理由是:
        1. pycharm 无法智能提示;
        2. 必须要有明确的文档说明,告知用户要传递的参数是什么;
        3.使用 exception_msg=None 作为关键字参数可以解决这两个问题;
        """
        return isinstance(self.value, str)
     '''
    @raise_exception
    def is_not_empty(self, exception_msg=None):
        return bool(self.value)


3、测试代码

atme/demo/validator_v2/test_validator.py

import pytest
from atme.demo.validator_v2.validator import Validator
def 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 items
test_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、要点小结

  • 使用 inspect.signature(func).bind(*args, **kwargs).arguments 获取方法中的参数名和对应的参数值;
  • 因为用户有可能使用两种方式给 exception_msg 传值,如:is_string('Jane', exception_msg='name must be string') is_string('Jane', 'name must be string')所以使用 kwargs.get('exception_msg', None) or bound_args.get('exception_msg', None) 来获取 exception_msg 的值;
  • 显示优于隐式,为了方便用户知道传递什么参数,在校验方法中显示指定关键字参数 exception_msg=None, 不要使用 **kwargs 这种写法。
  • 每个方法中都使用了@raise_exception 装饰器,如果校验方法越来越多,类中的所有方法都会被加上装饰器,重复会很明显,可以继续优化。


2、下节准备

  • 使用 RaiseExceptionMeta 元类隐式装饰 Validator 类中的所有校验方法

点击进入《Python装饰器从入门到进阶》总目录

目录
相关文章
|
SQL Oracle 关系型数据库
问题出在Debezium Oracle Connector的日志解析器上
问题出在Debezium Oracle Connector的日志解析器上
279 2
|
前端开发 测试技术 API
DDD领域驱动设计实战-分层架构及代码目录结构(上)
DDD领域驱动设计实战-分层架构及代码目录结构
1748 0
DDD领域驱动设计实战-分层架构及代码目录结构(上)
|
11月前
|
消息中间件 Ubuntu Java
Kafka安装部署
Kafka安装部署
|
10月前
|
Python
自动化微信朋友圈:Python脚本实现自动发布动态
本文介绍如何使用Python脚本自动化发布微信朋友圈动态,节省手动输入的时间。主要依赖`pyautogui`、`time`、`pyperclip`等库,通过模拟鼠标和键盘操作实现自动发布。代码涵盖打开微信、定位朋友圈、准备输入框、模拟打字等功能。虽然该方法能提高效率,但需注意可能违反微信使用条款,存在风险。定期更新脚本以适应微信界面变化也很重要。
813 61
自动化微信朋友圈:Python脚本实现自动发布动态
|
11月前
|
机器学习/深度学习
深入理解SVM中的核函数及其应用
深入理解SVM中的核函数及其应用
460 91
|
测试技术 索引 Python
|
11月前
|
API Android开发
Eclipse 工作空间(Workspace)
Eclipse 工作空间(Workspace)
182 9
|
机器学习/深度学习 存储 自然语言处理
大语言模型参数真的必须要万亿以上吗?
本文探讨了大语言模型(LLMs)的发展及其在自然语言处理领域的应用。随着模型规模的不断增大,文章分析了参数规模与性能之间的关系,并展示了不同规模模型的优势与挑战。此外,文中还提供了代码示例,介绍了参数设置的方法。未来研究方向包括模型压缩和多模态学习,以进一步优化模型性能。总之,选择合适的模型规模对于平衡性能和效率至关重要。
|
存储 弹性计算 搜索推荐
快速部署 Qdrant 社区版
Qdrant是一个矢量相似性搜索引擎,提供生产就绪服务和方便的 API,用于存储、搜索和管理具有额外负载的点(即矢量)。您可以将有效负载视为附加信息,可以帮助您深入搜索并接收可以提供给用户的有用信息。本文介绍如何使用计算巢快速部署Qdrant服务。
快速部署 Qdrant 社区版
|
存储 分布式计算 固态存储
Hadoop性能优化硬件和网络优化
【6月更文挑战第7天】
242 3