Дескриптор#

class IPAddress:

    def __init__(self, ip, mask):
        self._ip = ip
        self._mask = mask

    @property
    def ip(self):
        return self._ip

    @ip.setter
    def ip(self, value):
        if not isinstance(value, str):
            raise TypeError('Wrong data type, expected str')
        self._ip = value

    @property
    def mask(self):
        return self._mask

    @mask.setter
    def mask(self, value):
        if not isinstance(value, int):
            raise TypeError('Wrong data type, expected int')
        self._mask = mask
class Integer:
    def __init__(self, name):
        self.name = name

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

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Wrong data type, expected int')
        instance.__dict__[self.name] = value

class String:
    def __init__(self, name):
        self.name = name

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

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError('Wrong data type, expected str')
        instance.__dict__[self.name] = value

Дескриптор обязательно должен быть указан на уровне класса:

class IPAddress:
    mask = Integer('mask')
    ip = String('ip')

    def __init__(self, ip, mask):
        self._ip = ip
        self._mask = mask


In [90]: ip1 = IPAddress('10.1.1.1', 28)

In [96]: ip1.mask = '24'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-96-247b3f37d10f> in <module>
----> 1 ip1.mask = '24'

<ipython-input-93-5812cdd26ed1> in __set__(self, instance, value)
      8         def __set__(self, instance, value):
      9             if not isinstance(value, int):
---> 10                 raise TypeError('Wrong data type, expected int')
     11             instance.__dict__[self.name] = value
     12

TypeError: Wrong data type, expected int

In [97]: ip1.ip = 142
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-97-24102e80dc3a> in <module>
----> 1 ip1.ip = 142

<ipython-input-93-5812cdd26ed1> in __set__(self, instance, value)
     20         def __set__(self, instance, value):
     21             if not isinstance(value, str):
---> 22                 raise TypeError('Wrong data type, expected str')
     23             instance.__dict__[self.name] = value

TypeError: Wrong data type, expected str

Оптимизированный вариант:

class Typed:
    attr_type = object

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

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

    def __set__(self, instance, value):
        if not isinstance(value, self.attr_type):
            raise TypeError(f'Wrong data type, expected {self.attr_type}')
        instance.__dict__[self.name] = value

class Integer(Typed):
    attr_type = int

class String(Typed):
    attr_type = str

Замыкания вместо дескриптора для проверки типа#

In [74]: def typed(name, attr_type):
    ...:     value = '_' + name
    ...:
    ...:     @property
    ...:     def attribute(self):
    ...:         return getattr(self, value)
    ...:
    ...:     @attribute.setter
    ...:     def attribute(self, new_value):
    ...:         if not isinstance(new_value, attr_type):
    ...:             raise TypeError(f'Wrong data type, expected {attr_type}')
    ...:         self.value = new_value
    ...:
    ...:     return attribute
    ...:

In [75]: class IPAddress:
    ...:     ip = typed('ip', str)
    ...:     mask = typed('mask', int)
    ...:
    ...:     def __init__(self, ip, mask):
    ...:         self.ip = ip
    ...:         self.mask = mask
    ...:

In [76]: ip1 = IPAddress('10.1.1.1', 28)

In [77]: ip1.mask = '24'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-77-247b3f37d10f> in <module>
----> 1 ip1.mask = '24'

<ipython-input-74-4348b0de06dc> in attribute(self, new_value)
      9     def attribute(self, new_value):
     10         if not isinstance(new_value, attr_type):
---> 11             raise TypeError(f'Wrong data type, expected {attr_type}')
     12         setattr(self, value, new_value)
     13

TypeError: Wrong data type, expected <class 'int'>

In [80]: ip1.mask?
Type:        property
String form: <property object at 0xb4203aa4>
Docstring:   <no docstring>

Дополнительные материалы#