我想创建多个具有很多必填字段和可选字段的python类。在初始化期间,我要检查是否所有必需的属性都已传递,并对所有属性进行验证。
这是我想出的解决方案。但是与此有关的问题是,IDE不再自动建议类属性。
有没有更好的解决方案来解决我的问题?
class Field(object):
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
class IntField(Field):
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError(f'expecting integer in {self.name} got {value}')
instance.__dict__[self.name] = value
class StrField(Field):
def __set__(self, instance, value):
if not isinstance(value, six.string_types):
raise ValueError(f'expecting string in {self.name} got {value}')
instance.__dict__[self.name] = value
class PropertiesCreator(type):
def __new__(mcs, classname, baseclasses, props):
c_props = props.get('_required_properties', ())
for name, cname in c_props:
props[name] = cname()
c_props = props.get('_optional_properties', ())
for name, cname in c_props:
props[name] = cname()
return super(PropertiesCreator, mcs).__new__(mcs, classname, baseclasses, props)
class Schema(object):
_required_properties = ()
_optional_properties = ()
def __init__(self, **kwargs):
for name, _ in self._required_properties:
try:
value = kwargs.pop(name)
except KeyError:
raise RequiredAttributeError("Required %s is missing" % name)
else:
setattr(self, name, value)
for name, _ in self._optional_properties:
try:
value = kwargs.pop(name)
except KeyError:
pass
else:
setattr(self, name, value)
if kwargs:
raise InvalidAttributeError("Invalid items ")
def required_attributes(self):
return self._required_properties
def optional_attributes(self):
return self._optional_properties
@six.add_metaclass(PropertiesCreator)
class CheckpointSchema(Schema):
_required_properties = (
("name", StrField),
("is_enabled", BoolField)
)
def __init__(self, **kwargs):
super(CheckpointSchema, self).__init__(**kwargs)
答案 0 :(得分:0)
使用元类检查属性的方式是开放式的,仅在运行时运行的代码才能实际定义参数是必需参数还是有效参数。这意味着,IDE无法通过没有的方式猜测您想要的验证模式并与之一起工作-您只能通过编写与代码一起使用的IDE扩展来确保它。
但是,如果您的对象类格式正确,{{IDE}使用Python本身导入一个包含您的类的模块(而不是通过尝试静态检查实际的键入代码)的可能性很大。 1}}方法,其中所有必需参数均为必需,所有可选参数均带有默认前哨值,那么它将起作用。
因此,您必须以一种能真正生成带有适当签名的__init__
函数的方式编写元类。尽管有多种方法可以使用__init__
模块中的内容以编程方式设置函数签名,或者使用inspect
+ types.CodeType
创建函数,但这将花费很长时间才能开始工作,并且变得而不是简单地将字符串与所需的方法签名组成并使用types.FunctionType
使其可读性差。而且,这可以利用注释,因此您甚至可以从IDE中获得额外的帮助。
因此,您的代码可能会变成类似(很抱歉-Python 3.6-为了使其也与Python 2.7兼容,您将需要进行更多输入):
exec
答案 1 :(得分:0)
您也可以使用pyfields
:
from pyfields import field, init_fields
class Position(object):
x: int = field(check_type=True, validators=lambda x: x > 0)
y: int = field(check_type=True,
validators={'y should be between 0 and 100': lambda y: y > 0 and y < 100})
@init_fields
def __init__(self, msg="hello world!"):
print(msg)
p = Position(x=1, y=12) # ok
p.x = '1' # raises TypeError see below
p.y = 101 # raises ValidationError see below
收益
TypeError: Invalid value type provided for '<...>.Position.x'.
Value should be of type <class 'int'>. Instead, received a 'str': '1'
和
ValidationError[ValueError]: Error validating [<...>.Position.y=101].
InvalidValue: y should be between 0 and 100.
Function [<lambda>] returned [False] for value 101.
IDE自动补全功能在创建对象(或引用类型的任何地方)后就可以使用,但是当前对于生成的构造函数的签名不起作用;这是由于生成了构造函数,IDE很难遵循。我将尝试与PyCharm一起改善这一点。
有关详细信息,请参见pyfields
documentation。我是您可能已经猜到的作者;)