Дескриптор Python для проверки типов и неизменности - PullRequest
0 голосов
/ 29 августа 2018

Прочитайте кулинарную книгу Python и ознакомьтесь с дескрипторами, в частности, с примером применения типов при использовании атрибутов класса. Я пишу несколько классов, где это было бы полезно, но я также хотел бы обеспечить неизменность. Как это сделать? Дескриптор проверки типа, адаптированный из книги:

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

        for key, value in kwargs.items():
            setattr(self, key, value)

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


# by default allows None
class Typed(Descriptor):
    def __init__(self, expected_types=None, **kwargs):
        self.expected_types = expected_types

        super().__init__(**kwargs)

    def __set__(self, instance, value):
        if value is not None and not isinstance(value, self.expected_types):
            raise TypeError('Expected: {}'.format(str(self.expected_types)))

        super(Typed, self).__set__(instance, value)

class T(object):
    v = Typed(int)

    def __init__(self, v):
        self.v = v

Попытка # 1: добавить атрибут self.is_set в типизированный

# by default allows None
class ImmutableTyped(Descriptor):
    def __init__(self, expected_types=None, **kwargs):
        self.expected_types = expected_types
        self.is_set = False

        super().__init__(**kwargs)

    def __set__(self, instance, value):
        if self.is_set:
            raise ImmutableException(...)
        if value is not None and not isinstance(value, self.expected_types):
            raise TypeError('Expected: {}'.format(str(self.expected_types)))

        self.is_set = True

        super(Typed, self).__set__(instance, value)

Неправильно, потому что при выполнении следующего действия ImmutableTyped является «глобальным» в том смысле, что он является единичным для всех экземпляров класса. Когда создается экземпляр t2, is_set уже истина от предыдущего объекта.

class T(object):
    v = ImmutableTyped(int)

    def __init__(self, v):
        self.v = v

t1 = T()
t2 = T()  # fail when instantiating

Попытка # 2: Экземпляр мысли в __set__ относится к классу, содержащему атрибут, поэтому попытался проверить, является ли instance.__dict__[self.name] типизированным. Это тоже неправильно.

Идея № 3: Сделать Typed более похожим на @property, приняв метод fget, возвращающий __dict__ из T экземпляров. Это потребует определения функции в T, аналогичной:

@Typed
def v(self):
    return self.__dict__

что кажется неправильным.

Как реализовать неизменяемость И проверку типов в качестве дескриптора?

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Не удалось создать такой дескриптор. Возможно, это также излишне сложно. Следующий метод + property использовать достаточно.

# this also allows None to go through
def check_type(data, expected_types):
    if data is not None and not isinstance(data, expected_types):
        raise TypeError('Expected: {}'.format(str(expected_types)))

    return data

class A():
    def __init__(self, value=None):
        self._value = check_type(value, (str, bytes))

    @property
    def value(self):
        return self._value


foo = A()
print(foo.value)    # None
foo.value = 'bla'   # AttributeError
bar = A('goosfraba')
print(bar.value)    # goosfraba
bar.value = 'bla'   # AttributeError
0 голосов
/ 29 августа 2018

Вот мой подход к проблеме:

class ImmutableTyped:
    def __set_name__(self, owner, name):
        self.name = name

    def __init__(self, *, immutable=False, types=None)
        self.immutable == immutable is True
        self.types = types if types else []

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

    def __set__(self, instance, value):
        if self.immutable is True:
            raise TypeError('read-only attribute')
        elif not any(isinstance(value, cls)
                     for cls in self.types):
            raise TypeError('invalid argument type')
        else:
           instance.__dict__[self.name] = value

Примечание: __set_name__ может использоваться, чтобы позволить вам не указывать имя атрибута при инициализации. Это означает, что вы можете просто сделать:

class Foo:
    bar = ImmutableTyped()

и экземпляр ImmutableTyped автоматически будет иметь атрибут name bar, поскольку я набрал это для метода __set_name__.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...