创建带有必需和可选属性的python类?

时间:2018-06-23 09:22:25

标签: python metaclass

我想创建多个具有很多必填字段和可选字段的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)

2 个答案:

答案 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。我是您可能已经猜到的作者;)