使用自定义方法验证数据类字段?
Validate dataclass field with custom defined method?
在使用数据类时,类型提示很好,但我也在寻找的是传递值的验证(例如最大长度的字符串50, int 上限为 100 etc)
有没有办法验证传递的值?例如,Pydantic 有这些 Validators。我正在寻找不添加外部库的本机内容。我唯一的解决办法是:
from dataclasses import dataclass
def validate_str(max_length):
def _validate(f):
def wrapper(self, value):
if type(value) is not str:
raise TypeError(f"Expected str, got: {type(value)}")
elif len(value) > max_length:
raise ValueError(
f"Expected string of max length {max_length}, got string of length {len(value)} : {value}" # noqa
)
else:
return f(self, value)
return wrapper
return _validate
@dataclass
class Example:
"""Class for keeping track of an item in inventory."""
@property
def name(self):
return self._name
@name.setter
@validate_str(max_length=50)
def name(self, value):
self._name = value
其中 validate_str
只是一种自定义装饰器方法,用于检查所提供值的长度,但我又重复了一遍。
我想以某种方式在数据类属性的同一行中传递验证器:
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str = validate_somehow()
unit_price: float = validate_somehow()
quantity_on_hand: int = 0
理想的方法是使用 Validator
example from the Python how-to guide on descriptors.
的修改版本
例如:
from abc import ABC, abstractmethod
from dataclasses import dataclass, MISSING
class Validator(ABC):
def __set_name__(self, owner, name):
self.private_name = '_' + name
def __get__(self, obj, obj_type=None):
return getattr(obj, self.private_name)
def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)
@abstractmethod
def validate(self, value):
"""Note: subclasses must implement this method"""
class String(Validator):
# You may or may not want a default value
def __init__(self, default: str = MISSING, minsize=None, maxsize=None, predicate=None):
self.default = default
self.minsize = minsize
self.maxsize = maxsize
self.predicate = predicate
# override __get__() to return a default value if one is not passed in to __init__()
def __get__(self, obj, obj_type=None):
return getattr(obj, self.private_name, self.default)
def validate(self, value):
if not isinstance(value, str):
raise TypeError(f'Expected {value!r} to be an str')
if self.minsize is not None and len(value) < self.minsize:
raise ValueError(
f'Expected {value!r} to be no smaller than {self.minsize!r}'
)
if self.maxsize is not None and len(value) > self.maxsize:
raise ValueError(
f'Expected {value!r} to be no bigger than {self.maxsize!r}'
)
if self.predicate is not None and not self.predicate(value):
raise ValueError(
f'Expected {self.predicate} to be true for {value!r}'
)
@dataclass
class A:
y: str = String(default='DEFAULT', minsize=5, maxsize=10, predicate=str.isupper) # Descriptor instance
x: int = 5
a = A()
print(a)
a = A('TESTING!!')
print(a)
try:
a.y = 'testing!!'
except Exception as e:
print('Error:', e)
try:
a = A('HEY')
except Exception as e:
print('Error:', e)
try:
a = A('HELLO WORLD!')
except Exception as e:
print('Error:', e)
try:
a.y = 7
except Exception as e:
print('Error:', e)
输出:
A(y='DEFAULT', x=5)
A(y='TESTING!!', x=5)
Error: Expected <method 'isupper' of 'str' objects> to be true for 'testing!!'
Error: Expected 'HEY' to be no smaller than 5
Error: Expected 'HELLO WORLD!' to be no bigger than 10
Error: Expected 7 to be an str
在使用数据类时,类型提示很好,但我也在寻找的是传递值的验证(例如最大长度的字符串50, int 上限为 100 etc)
有没有办法验证传递的值?例如,Pydantic 有这些 Validators。我正在寻找不添加外部库的本机内容。我唯一的解决办法是:
from dataclasses import dataclass
def validate_str(max_length):
def _validate(f):
def wrapper(self, value):
if type(value) is not str:
raise TypeError(f"Expected str, got: {type(value)}")
elif len(value) > max_length:
raise ValueError(
f"Expected string of max length {max_length}, got string of length {len(value)} : {value}" # noqa
)
else:
return f(self, value)
return wrapper
return _validate
@dataclass
class Example:
"""Class for keeping track of an item in inventory."""
@property
def name(self):
return self._name
@name.setter
@validate_str(max_length=50)
def name(self, value):
self._name = value
其中 validate_str
只是一种自定义装饰器方法,用于检查所提供值的长度,但我又重复了一遍。
我想以某种方式在数据类属性的同一行中传递验证器:
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str = validate_somehow()
unit_price: float = validate_somehow()
quantity_on_hand: int = 0
理想的方法是使用 Validator
example from the Python how-to guide on descriptors.
例如:
from abc import ABC, abstractmethod
from dataclasses import dataclass, MISSING
class Validator(ABC):
def __set_name__(self, owner, name):
self.private_name = '_' + name
def __get__(self, obj, obj_type=None):
return getattr(obj, self.private_name)
def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)
@abstractmethod
def validate(self, value):
"""Note: subclasses must implement this method"""
class String(Validator):
# You may or may not want a default value
def __init__(self, default: str = MISSING, minsize=None, maxsize=None, predicate=None):
self.default = default
self.minsize = minsize
self.maxsize = maxsize
self.predicate = predicate
# override __get__() to return a default value if one is not passed in to __init__()
def __get__(self, obj, obj_type=None):
return getattr(obj, self.private_name, self.default)
def validate(self, value):
if not isinstance(value, str):
raise TypeError(f'Expected {value!r} to be an str')
if self.minsize is not None and len(value) < self.minsize:
raise ValueError(
f'Expected {value!r} to be no smaller than {self.minsize!r}'
)
if self.maxsize is not None and len(value) > self.maxsize:
raise ValueError(
f'Expected {value!r} to be no bigger than {self.maxsize!r}'
)
if self.predicate is not None and not self.predicate(value):
raise ValueError(
f'Expected {self.predicate} to be true for {value!r}'
)
@dataclass
class A:
y: str = String(default='DEFAULT', minsize=5, maxsize=10, predicate=str.isupper) # Descriptor instance
x: int = 5
a = A()
print(a)
a = A('TESTING!!')
print(a)
try:
a.y = 'testing!!'
except Exception as e:
print('Error:', e)
try:
a = A('HEY')
except Exception as e:
print('Error:', e)
try:
a = A('HELLO WORLD!')
except Exception as e:
print('Error:', e)
try:
a.y = 7
except Exception as e:
print('Error:', e)
输出:
A(y='DEFAULT', x=5)
A(y='TESTING!!', x=5)
Error: Expected <method 'isupper' of 'str' objects> to be true for 'testing!!'
Error: Expected 'HEY' to be no smaller than 5
Error: Expected 'HELLO WORLD!' to be no bigger than 10
Error: Expected 7 to be an str