"""Currency class and a set of exceptions. Face values are stored as decimals*. * Decimal is used to avoid problems of rounding float numbers. So first two digits to the right are used to store a fraction part. You can simply divide this number by 100 to get a floating-point number. Currency string has following format: ;;;{},{} ; {} with currency symbol used as a placeholder for where to put digits. Example: RUB;Russian ruble;8;{}коп.,{}₽;1,5,10,50,100,200,500,1000 USD;US Dollar;6;{}¢,${};1,5,10,25,50,100 """ from __future__ import annotations from operator import mul from typing import Dict, List, TypedDict __all__ = [ "Currency", "BaseCurrencyError", "CurrencyIsNotSupportedError", "CurrenciesCoinCountMismatchError", "CurrencyMismatchError"] class BaseCurrencyError(Exception): """Base class for all currency exeptions.""" def __init__(self, message=None, *args, **kwargs) -> None: if message is None: message = self.__doc__ super().__init__(message, *args, **kwargs) class CurrencyIsNotSupportedError(BaseCurrencyError): """Currency is not supported.""" class CurrencyMismatchError(BaseCurrencyError): """Currencies doesn't match, but they must do so.""" def __init__(self, extra=None): if not extra is None: super().__init__(f"{self.__doc__} {extra}") else: super().__init__(self.__doc__) class CurrenciesCoinCountMismatchError(BaseCurrencyError): """Length of list supplied doesn't corresponds coins count.""" class Currency: """Currency's representation and methods to work with it. Arguments: iso_code -- ISO code of a currency; name -- full name of a currency; coins_count -- number of face values of coins; fraction_symbol -- a symbol or word for a fraction of currency (e.g. c, cent); currency_symbol -- a symbol or word for a currency (e.g. $); face_values -- list of values for each face value in decimal (e.g. $476.40 is 47640); """ def __init__(self, iso_code: str, name: str, coins_count: int, fraction_symbol: str, currency_symbol: str, face_values: List[int]): self._iso = iso_code.upper() self._name = name self._coins_count = coins_count self._fraction_symbol = fraction_symbol self._currency_symbol = currency_symbol self._face_values = face_values @property def iso(self) -> str: """ISO code of a currency.""" return self._iso @property def name(self) -> str: """Full name of a currency.""" return self._name @property def count(self): """Count of coins of a currency.""" return self._coins_count @property def fraction_symbol(self) -> str: """Returns a symbol or word for a fraction of a currency.""" return self._fraction_symbol @property def currency_symbol(self) -> str: """Returns a symbol or word for a currency.""" return self._currency_symbol @property def face_values(self) -> List[int]: """Face values of coins. From least to most.""" return self._face_values def multiply(self, lst: List[int]) -> List[float]: """Multiplies a list by face values. Length of list must match currency's coins count.""" if len(lst) != len(self): raise CurrenciesCoinCountMismatchError return list(map(mul, lst, self.face_values)) @staticmethod def from_string(currency: str) -> Currency: """Creates a Currency object from its string representation.""" iso, name, count, syms, fv = currency.split(';') frac_sym, cur_sym = syms.split(',') return Currency(iso, name, int(count), frac_sym, cur_sym, list(map(int, fv.split(',')))) def __mul__(self, lst: List[int]) -> List[float]: return self.multiply(lst) def __len__(self) -> int: return self.count def __str__(self): return f"{self.iso};{self.name};{self.count};" \ f"{self.fraction_symbol},{self.currency_symbol};" \ f"{','.join(list(map(str, self.face_values)))}"