一、前置說(shuō)明
1、本節(jié)目標(biāo)
- 了解
__getattribute__
的特性 - 使用
__getattribute__
結(jié)合Validator
類(lèi)中的方法,讓編輯器Pycharm
智能提示ParamValidator
類(lèi)中的方法
2、相關(guān)回顧
- 基于 Validator 類(lèi)實(shí)現(xiàn) ParamValidator,用于校驗(yàn)函數(shù)參數(shù)
二、操作步驟
1、項(xiàng)目目錄
-
atme
:@me
用于存放臨時(shí)的代碼片斷或其它內(nèi)容。 -
pyparamvalidate
: 新建一個(gè)與項(xiàng)目名稱(chēng)同名的package,為了方便發(fā)布至pypi
。 -
core
: 用于存放核心代碼。 -
tests
: 用于存放測(cè)試代碼。 -
utils
: 用于存放一些工具類(lèi)或方法。
2、代碼實(shí)現(xiàn)
atme/demo/validator_v6/param_validator.py
import inspect
from functools import wraps
from typing import TypeVar, Callable
from atme.demo_validator.validator_v6.validator import Validator
Self = TypeVar('Self', bound='ParameterValidator')
class ParameterValidator:
def __init__(self, param_name: str, param_rule_des=None):
"""
:param param_name: 參數(shù)名
:param param_rule_des: 該參數(shù)的規(guī)則描述
"""
self.param_name = param_name
self.param_rule_des = param_rule_des
self._validators = []
def __getattribute__(self, name: str):
"""
__getattribute__ 在每次訪問(wèn)對(duì)象的屬性時(shí)都會(huì)觸發(fā),不管屬性是否存在。
以用戶(hù)使用 ParamValidator("param").is_string(exception_msg='param must be string').is_not_empty() 為例,代碼執(zhí)行過(guò)程如下:
1. 當(dāng)用戶(hù)調(diào)用 ParamValidator("param").is_string(exception_msg='param must be string') 時(shí),
2. 由于 is_string 方法不存在,__getattr__ 方法被調(diào)用,返回 validator_method 函數(shù)(此時(shí)未被調(diào)用),is_string 方法實(shí)際上是 validator_method 函數(shù)的引用,
3. 當(dāng)執(zhí)行 is_string(exception_msg='param must be string') 時(shí),is_string 方法被調(diào)用, 使用關(guān)鍵字參數(shù)傳遞 exception_msg='param must be string',
4. 實(shí)際上是執(zhí)行了 validator_method(exception_msg='param must be string') , validator_method 函數(shù)完成調(diào)用后,執(zhí)行函數(shù)體中的邏輯:
- 向 self._validators 中添加了一個(gè)元組 ('is_string', (), {'exception_msg': 'param must be string'})
- 返回 self 對(duì)象
5. self 對(duì)象繼續(xù)調(diào)用 is_not_empty(), 形成鏈?zhǔn)秸{(diào)用效果,此時(shí)的 validator_method 函數(shù)的引用就是 is_not_empty, 調(diào)用過(guò)程與 1-4 相同。
"""
# 如果獲取到已存在的屬性, 則使用 object.__getattribute__(self, name) 直接獲取對(duì)象的屬性值
if name in ['param_name', 'param_rule_des', '_validators']:
return object.__getattribute__(self, name)
# 如果獲取到不存在的屬性,則創(chuàng)建了函數(shù) validator_method
def validator_method(*args, **kwargs):
self._validators.append((name, args, kwargs))
return self
return validator_method
def __call__(self, func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
# 獲取函數(shù)的參數(shù)和參數(shù)值
bound_args = inspect.signature(func).bind(*args, **kwargs).arguments
if self.param_name in kwargs:
# 如果函數(shù)被裝飾,且以關(guān)鍵字參數(shù)傳值,則從 kwargs 中取參數(shù)值
value = kwargs[self.param_name]
else:
# 如果函數(shù)被裝飾,且以位置參數(shù)傳值,則從 bound_args 中取參數(shù)值
value = bound_args.get(self.param_name)
# 實(shí)例化 Validator 對(duì)象
validator = Validator(value, field=self.param_name, rule_des=self.param_rule_des)
# 遍歷所有校驗(yàn)器(注意:這里使用 vargs, vkwargs,避免覆蓋原函數(shù)的 args, kwargs)
for method_name, vargs, vkwargs in self._validators:
# 通過(guò) 函數(shù)名 反射獲取校驗(yàn)函數(shù)對(duì)象
validate_method = getattr(validator, method_name)
# 執(zhí)行校驗(yàn)函數(shù)
validate_method(*vargs, **vkwargs)
# 執(zhí)行原函數(shù)
return func(*args, **kwargs)
return wrapper
'''
==============================分隔符===============================
以下所有方法,是從 Validator 類(lèi)中復(fù)制過(guò)來(lái),目的是:
- 為了讓編輯器如 Pycharm 智能提示 ParameterValidator 本類(lèi)中可以使用的校驗(yàn)方法;
- 這些方法僅供 Pycharm 智能提示使用,沒(méi)有任何實(shí)際作用;
可以是:
def is_string(self, exception_msg=None) -> Self:
...
也可以是:
def is_string(self, exception_msg=None) -> Self:
return isinstance(self.value, str)
- ParameterValidator 類(lèi)的實(shí)例通過(guò) __getattribute__ 方法動(dòng)態(tài)收集用戶(hù)的調(diào)用方法;
- 然后使用 __call__ 方法反射調(diào)用 Validator 類(lèi)中的校驗(yàn)方法
在模塊中定義了: Self = TypeVar('Self', bound='ParameterValidator'),目的是:
- 方便從 Validator 類(lèi)中復(fù)制校驗(yàn)方法,粘貼之后不做任何代碼層面的修改:
- 方便鏈?zhǔn)秸{(diào)用,如: @ParameterValidator("param").is_string().is_not_empty()
'''
def is_string(self, exception_msg=None) -> Self:
return isinstance(self.value, str)
def is_not_empty(self, exception_msg=None) -> Self:
return bool(self.value)
3、測(cè)試代碼
atme/demo/validator_v6/test_param_validator.py
import pytest
from atme.demo.validator_v6.param_validator import ParameterValidator
def test_is_string_validator_passing_01():
"""
校驗(yàn)一個(gè)參數(shù)
"""
@ParameterValidator("param").is_string(exception_msg='param must be string')
def example_function(param):
print(param)
return param
assert 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():
"""
校驗(yàn)多個(gè)參數(shù)
"""
@ParameterValidator("param2").is_string().is_not_empty()
@ParameterValidator("param1").is_string().is_not_empty()
def example_function(param1, param2):
print(param1, param2)
return param1, param2
assert 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、日志輸出
執(zhí)行 test
的日志如下,驗(yàn)證通過(guò):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-809136.html
============================= test session starts =============================
collecting ... collected 2 items
test_param_validator.py::test_is_string_validator_passing_01 PASSED [ 50%]test
param error: "123" is invalid. due to: param must be string
test_param_validator.py::test_is_string_validator_passing_02 PASSED [100%]test1 test2
param2 error: "123" is invalid.
============================== 2 passed in 0.01s ==============================
三、后置說(shuō)明
1、要點(diǎn)小結(jié)
-
__getattribute__
在每次訪問(wèn)對(duì)象的屬性時(shí)都會(huì)觸發(fā),不管屬性是否存在。 - 通過(guò)重寫(xiě)
__getattribute__
,可以自定義屬性的獲取邏輯,實(shí)現(xiàn)了對(duì)特定屬性的直接訪問(wèn)(param_name
、param_rule_des
、_validators
),而對(duì)于其他屬性,則創(chuàng)建名為validator_method
的函數(shù),將其作為屬性返回。 - 從
Validator
類(lèi)中復(fù)制過(guò)來(lái)的校驗(yàn)方法,是為了讓編輯器如Pycharm
智能提示ParameterValidator
本類(lèi)中可以使用的校驗(yàn)方法,沒(méi)有任何實(shí)際作用。 - 在模塊中定義
Self = TypeVar('Self', bound='ParameterValidator')
,是為了方便鏈?zhǔn)秸{(diào)用,如@ParameterValidator("param").is_string().is_not_empty()
。 - 經(jīng)過(guò)優(yōu)化后,
Pycharm
可以正常智能提示可調(diào)用的校驗(yàn)方法:
2、下節(jié)準(zhǔn)備
- validator 常用校驗(yàn)器的實(shí)現(xiàn)
點(diǎn)擊進(jìn)入《Python裝飾器從入門(mén)到進(jìn)階》總目錄文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-809136.html
到了這里,關(guān)于優(yōu)化 ParamValidator,讓編輯器Pycharm智能提示校驗(yàn)方法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!