8.13 实现数据模型的类型约束

    在这个问题中,你需要在对某些实例属性赋值时进行检查。所以你要自定义属性赋值函数,这种情况下最好使用描述器。

    下面的代码使用描述器实现了一个系统类型和赋值验证框架:

    这些类就是你要创建的数据模型或类型系统的基础构建模块。下面就是我们实际定义的各种不同的数据类型:

    1. class Integer(Typed):
    2. expected_type = int
    3.  
    4. class UnsignedInteger(Integer, Unsigned):
    5. pass
    6.  
    7. class Float(Typed):
    8. expected_type = float
    9.  
    10. class UnsignedFloat(Float, Unsigned):
    11. pass
    12.  
    13. class String(Typed):
    14. expected_type = str
    15.  
    16. class SizedString(String, MaxSized):
    17. pass

    然后使用这些自定义数据类型,我们定义一个类:

    然后测试这个类的属性赋值约束,可发现对某些属性的赋值违法了约束是不合法的:

    1. >>> s.name
    2. 'ACME'
    3. >>> s.shares = 75
    4. >>> s.shares = -10
    5. Traceback (most recent call last):
    6. File "<stdin>", line 1, in <module>
    7. File "example.py", line 17, in __set__
    8. super().__set__(instance, value)
    9. File "example.py", line 23, in __set__
    10. raise ValueError('Expected >= 0')
    11. ValueError: Expected >= 0
    12. >>> s.price = 'a lot'
    13. Traceback (most recent call last):
    14. File "<stdin>", line 1, in <module>
    15. File "example.py", line 16, in __set__
    16. raise TypeError('expected ' + str(self.expected_type))
    17. TypeError: expected <class 'float'>
    18. >>> s.name = 'ABRACADABRA'
    19. Traceback (most recent call last):
    20. File "<stdin>", line 1, in <module>
    21. File "example.py", line 17, in __set__
    22. super().__set__(instance, value)
    23. File "example.py", line 35, in __set__
    24. raise ValueError('size must be < ' + str(self.size))
    25. ValueError: size must be < 8
    26. >>>

    另外一种方式是使用元类:

    1. # A metaclass that applies checking
    2. def __new__(cls, clsname, bases, methods):
    3. # Attach attribute names to the descriptors
    4. for key, value in methods.items():
    5. if isinstance(value, Descriptor):
    6. value.name = key
    7. return type.__new__(cls, clsname, bases, methods)
    8.  
    9. # Example
    10. class Stock2(metaclass=checkedmeta):
    11. name = SizedString(size=8)
    12. shares = UnsignedInteger()
    13. price = UnsignedFloat()
    14.  
    15. def __init__(self, name, shares, price):
    16. self.name = name
    17. self.shares = shares
    18. self.price = price

    本节使用了很多高级技术,包括描述器、混入类、 的使用、类装饰器和元类。不可能在这里一一详细展开来讲,但是可以在8.9、8.18、9.19小节找到更多例子。但是,我在这里还是要提一下几个需要注意的点。

    首先,在 Descriptor 基类中你会看到有个 set() 方法,却没有相应的 方法。如果一个描述仅仅是从底层实例字典中获取某个属性值的话,那么没必要去定义 get() 方法。

    所有描述器类都是基于混入类来实现的。比如 Unsigned 和 要跟其他继承自 Typed 类混入。这里利用多继承来实现相应的功能。

    混入类的一个比较难理解的地方是,调用 super() 函数时,你并不知道究竟要调用哪个具体类。你需要跟其他类结合后才能正确的使用,也就是必须合作才能产生效果。

    所有方法中,类装饰器方案应该是最灵活和最高明的。首先,它并不依赖任何其他新的技术,比如元类。其次,装饰器可以很容易的添加或删除。

    最后,装饰器还能作为混入类的替代技术来实现同样的效果;

    1. # Decorator for applying type checking
    2. def Typed(expected_type, cls=None):
    3. if cls is None:
    4. return lambda cls: Typed(expected_type, cls)
    5. super_set = cls.__set__
    6.  
    7. def __set__(self, instance, value):
    8. if not isinstance(value, expected_type):
    9. raise TypeError('expected ' + str(expected_type))
    10. super_set(self, instance, value)
    11.  
    12. cls.__set__ = __set__
    13. return cls
    14.  
    15.  
    16. # Decorator for unsigned values
    17. def Unsigned(cls):
    18. super_set = cls.__set__
    19.  
    20. def __set__(self, instance, value):
    21. if value < 0:
    22. raise ValueError('Expected >= 0')
    23. super_set(self, instance, value)
    24.  
    25. cls.__set__ = __set__
    26. return cls
    27.  
    28.  
    29. # Decorator for allowing sized values
    30. def MaxSized(cls):
    31.  
    32. def __init__(self, name=None, **opts):
    33. if 'size' not in opts:
    34. raise TypeError('missing size option')
    35. super_init(self, name, **opts)
    36.  
    37. cls.__init__ = __init__
    38.  
    39. super_set = cls.__set__
    40.  
    41. def __set__(self, instance, value):
    42. if len(value) >= self.size:
    43. raise ValueError('size must be < ' + str(self.size))
    44. super_set(self, instance, value)
    45.  
    46. cls.__set__ = __set__
    47. return cls
    48.  
    49.  
    50. # Specialized descriptors
    51. @Typed(int)
    52. class Integer(Descriptor):
    53. pass
    54.  
    55.  
    56. @Unsigned
    57. class UnsignedInteger(Integer):
    58. pass
    59.  
    60.  
    61. @Typed(float)
    62. class Float(Descriptor):
    63. pass
    64.  
    65.  
    66. @Unsigned
    67. class UnsignedFloat(Float):
    68. pass
    69.  
    70.  
    71. @Typed(str)
    72. class String(Descriptor):
    73. pass
    74.  
    75.  
    76. @MaxSized
    77. class SizedString(String):

    这种方式定义的类跟之前的效果一样,而且执行速度会更快。设置一个简单的类型属性的值,装饰器方式要比之前的混入类的方式几乎快100%。现在你应该庆幸自己读完了本节全部内容了吧?^_^

    原文: