(continued from previous page) ) if self.maxvalue is not None and value>self.maxvalue: raise ValueError( f'Expected { value !r} to be no more than { self.maxvalue !r} ' ) classString (Validator): def __init__ (self, minsize= None , maxsize= None , predicate= None ): self.minsize =minsize self.maxsize =maxsize self.predicate =predicate def validate(self, value): if not isinstance(value, str): raise TypeError(f'Expected { value !r} to be an str') if self.minsize is not None and len(value) <self.minsize: raise ValueError( f'Expected { value !r} to be no smaller than { self.minsize !r} ' ) if self.maxsize is not None and len(value) >self.maxsize: raise ValueError( f'Expected { value !r} to be no bigger than { self.maxsize !r} ' ) if self.predicate is not None and not self.predicate(value): raise ValueError( f'Expected { self.predicate } to be true for { value !r} ' ) classComponent : name =String(minsize=3, maxsize=10 , predicate=str.isupper) kind =OneOf('wood','metal','plastic') quantity=Number(minvalue =0) def __init__ (self, name, kind, quantity): self.name =name self.kind =kind self.quantity=quantity 2.3 Practical application Here’s how the data validators can be used in a real class: The descriptors prevent invalid instances from being created: (continues on next page) 8 >>> Component( 'Widget','metal',5) # Blocked: 'Widget' is not all uppercase Traceback (most recent call last): ... ValueError: Expected <method 'isupper' of 'str' objects> to be true for >>> Component( 'WIDGET','metle',5) # Blocked: 'metle' is misspelled Traceback (most recent call last): ... ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'} >>> Component( 'WIDGET','metal',-5) # Blocked: -5 is negative Traceback (most recent call last): ...