Python的类型检查系统

问题描述 投票:1回答:1

我试图使自定义类型的系统在Python。以下是代码。

from inspect import Signature, Parameter

class Descriptor():
    def __init__(self, name=None):
        self.name = name

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

    def __get__(self, instance, cls):
        return instance.__dict__[self.name]

class Typed(Descriptor):
    ty = object
    def __set__(self, instance, value):
        if not isinstance(value, self.ty):
            raise TypeError('Expected %s' %self.ty)
        super().__set__(instance, value)

class Integer(Typed):
    ty = int

class Float(Typed):
    ty = float

class String(Typed):
    ty = str

class Positive(Descriptor):
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super().__set__(instance, value)

class PosInteger(Integer, Positive):
    pass

class Sized(Descriptor):
    def __init__(self, *args, maxlen, **kwargs):
        self.maxlen = maxlen
        super().__init__(*args, **kwargs)

    def __set__(self, instance, value):
        if len(value) > self.maxlen:
            raise ValueError('TooBig')
        super().__set__(instance, value)

class SizedString(String, Sized):
    pass

def make_signature(names):
    return Signature([Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names])

class StructMeta(type):

    def __new__(cls, name, bases, clsdict):
        fields = [key for key, value in clsdict.items() if isinstance(value, Descriptor)]

        for name in fields:
            #print(type(clsdict[name]))
            clsdict[name].name = name

        clsobj = super().__new__(cls, name, bases, clsdict)
        sig = make_signature(fields)
        setattr(clsobj, '__signature__', sig)
        return clsobj

class Structure(metaclass = StructMeta):
    def __init__(self, *args, **kwargs):
        bound = self.__signature__.bind(*args, **kwargs)
        for name, value in bound.arguments.items():
            setattr(self, name, value)

使用上述类型的系统,我摆脱了所有的样板代码和重复的代码,我将不得不类(主要是内INIT)检查类型的写,验证值等。

通过使用上面的代码,我的课看起来这么简单

class Stock(Structure):
        name =  SizedString(maxlen=9)
        shares =  PosInteger()
        price = Float()

 stock = Stock('AMZN', 100, 1600.0)

到这里的东西很好地工作。现在我想延长这种类型的检查功能,并创造举行另一次类的对象类。例如价格现在不再是浮但其类型​​的价格(即,另一类价格)。

class Price(Structure):
    currency = SizedString(maxlen=3)
    value = Float() 

class Stock(Structure):
    name =  SizedString(maxlen=9)
    shares =  PosInteger()
    price = Price() # This won't work. 

这不会起作用,因为行“价格=价格()”将调用价格的构造和所期望的货币和价值传递给构造函数,因为价格是一个结构,而不是一个描述符。它抛出“类型错误:缺少必需的参数:‘货币’”。

但我想它的工作,并使它看起来像上面,因为在这一天价格的结尾也就像PosInteger一个类型,但同时它必须是结构太。即价格应该从结构上继承,但同时它必须是一个描述符太。

我可以把它定义另一个类的工作说“PriceType”

class Price(Structure):
    currency = SizedString(maxlen=3)
    value = Float()

class PriceType(Typed):
    ty = Price

class Stock(Structure):
    name =  SizedString(maxlen=9)
    shares =  PosInteger()
    price = PriceType()

stock = Stock('AMZN', 100, Price('INR', 2400.0))

但是,这看起来有点怪异 - 价格和PriceType两个差班。有人可以帮助我了解,如果我能避免产生PriceType类?

我也失去了一个功能,提供默认值的字段。

例如,我怎能0库存股场的默认值或货币字段的默认值价格为“美元”?即类似下面。

class Stock:
    def __init__(name, price, shares=0)

class Price
    def __init__(value, currency = 'USD')
python metaprogramming descriptor
1个回答
2
投票

一个快速的事有是有一个简单的功能,当你声明的领域,将建立的“PriceType”(及等价物)。

由于描述符类唯一本身是不需要的,和相对长的时间一类花费的创建是不是一个问题,因为在体内类字段在程序加载时才会创建,你应该罚款:

def typefield(cls, *args, extra_checkers = (), **kwargs):
    descriptor_class = type(
        cls.__name__,
        (Typed,) + extra_checkers,
        {'ty': cls}
    )
    return descriptor_class(*args, **kwargs)

而现在,这样的代码应该只是工作:

class Stock(Structure):
    name =  SizedString(maxlen=9)
    shares =  PosInteger()
    price = typefield(Price, "price")

(另外,还要注意的Python 3.6+有__set_name__方法纳入descriptor protocol - 如果你使用这个,你就不会需要通过字段名作为参数的默认描述__init__,和类型字段名的两倍)

更新

在你的评论,你缝牵连希望你Structure类自己工作的描述 - 这将不能很好地工作 - 描述__get____set__方法是类方法 - 要与你的结构的实际情况来填充的字段。

有什么可以做的是移动typefield上述方法在结构类方法,有它标注的默认参数你想要的,并为这些类型的字段,将自动以默认值创建一个实例创建一个新的中间描述符类时被读出。此外,ty可以仅仅是在描述一个实例属性,因此无需创建动态类的字段:

class StructField(Typed):
    def __init__(self, *args, ty=None, def_args=(), def_kw=None, **kw):
        self.def_args = def_args
        self.def_kw = def_kw or {}
        self.ty = ty
        super().__init__(*args, **kw)
    def __get__(self, instance, owner):
         if self.name not in instance.__dict__:
              instance.__dict__[self.name] = self.ty(*self.def_args, **self.def_kw)
         return super().__get__(instance, owner)


    ...

    class Structure(metaclass=StructMeta):
        ...
        @classmethod
        def field(cls, *args, **kw):  
         # Change the signature if you want extra parameters 
         # for the field, like extra validators, and such
            return StructField(ty=cls, def_args=args, def_kw=kw)

...

class Stock(Structure):
    ...
    price = Price.field("USD", 20.00)
© www.soinside.com 2019 - 2024. All rights reserved.