# Exercise 3.4 - Solution ## (a) Private attributes ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (b) Computed Attributes ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = 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 def sell(self, nshares): self.shares -= nshares ``` ## (c) Enforcing Type-Checking Rules ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError('Expected an integer') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, float): raise TypeError('Expected a float') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (d) Adding `__slots__` ```python # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError('Expected integer') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, float): raise TypeError('Expected float') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (e) Reconciling types ```python # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` [Back](ex3_4.md)