Система проверки типов Python - PullRequest
0 голосов
/ 05 февраля 2019

Я пытаюсь сделать систему пользовательских типов в 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)

До этого все работает нормально.Теперь я хочу расширить функциональность проверок этого типа и создать классы, содержащие объекты других классов.Например, цена теперь больше не является Float, а имеет тип Price (то есть цена другого класса).

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

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

Это не будет работать, потому что строка "price = Price ()" будет вызывать конструкторPrice и будет ожидать, что валюта и значение будут переданы в конструктор, потому что Price является структурой, а не дескриптором.Выдает «TypeError: отсутствует обязательный аргумент:« currency »».

Но я хочу, чтобы это работало и выглядело как выше, потому что в конце цена также является типом, подобным 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))

Но это выглядит немного странно - Price и PriceType как два разностных класса.Может ли кто-нибудь помочь мне понять, могу ли я избежать создания класса PriceType?

Я также теряю функциональность для предоставления значений по умолчанию для полей.

Например, как я могу сохранить значение по умолчанию для поля акции в Акции в 0 или значение по умолчанию для поля валюты в Прайсе в 'USD'?то есть что-то вроде ниже.

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

class Price
    def __init__(value, currency = 'USD')

1 Ответ

0 голосов
/ 06 февраля 2019

Быстрая вещь, которую нужно сделать, - это иметь простую функцию, которая будет создавать «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__, включенный в протокол дескриптора - если вы его используете, вам не понадобитсяпередать имя поля в качестве параметра в дескриптор по умолчанию __init__ и дважды ввести имена полей)

update

В своем комментарии вы указываете на необходимостьваши Structure классы работают как дескрипторы - это не сработает хорошо - дескрипторы __get__ и __set__ - это методы классов - вы хотите, чтобы поля были заполнены фактическими экземплярамиваши структуры.

Что можно сделать, это переместить описанный выше метод typefield в метод класса в Structure, сделать так, чтобы он аннотировал параметры по умолчанию, которые вы хотите, и создать новый промежуточный класс дескриптора для таких полейэто автоматически создаст экземпляр со значениями по умолчанию, когда он будет прочитан.Кроме того, 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...