class Validator: @classmethod def check(cls, value): return value class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f"expected {cls.expected_type}") return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError("Expected >= 0") return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: return ValueError("Must be non-empty") return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass class Stock: _types = (str, int, float) __slots__ = ["name", "_shares", "_price"] def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f"Stock({self.name!r}, {self.shares!r}, {self.price!r})" def __eq__(self, other): if not isinstance(other, Stock): return False return (self.name, self.shares, self.price) == ( other.name, other.shares, other.price, ) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def cost(self): return self.shares * self.price @property def shares(self): return self._shares @shares.setter def shares(self, value): PositiveInteger.check(value) self._shares = value @property def price(self): return self._price @price.setter def price(self, value): PositiveFloat.check(value) self._price = value def sell(self, num): self.shares -= num if self.shares < 0: self.shares = 0