Witam
Co sądzicie o takiej implementacji klasy Money?
import functools
from decimal import Decimal
from dataclasses import dataclass
@dataclass
class Currency:
name: str
symbol: str
exchange_rate: Decimal
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name!r}, {self.symbol!r}, {self.exchange_rate!r})"
@functools.total_ordering
class Money:
def __init__(self, amount: Decimal, currency: Currency, precision: int = 2):
self._amount = amount
self.precision = precision
self.currency = currency
@property
def amount(self) -> Decimal:
return self._amount.quantize(Decimal("10") ** -self.precision)
@amount.setter
def amount(self, value: Decimal) -> None:
self._amount = value
def __str__(self) -> str:
return f"{self.amount} {self.currency.symbol}"
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.amount!r}, {self.currency!r})"
def __add__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot add two {self.__class__.__name__} instances: "
"different currency property")
return Money(self.amount + other.amount, self.currency)
def __sub__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot subtract two {self.__class__.__name__} instances: "
"different currency property")
return Money(self.amount - other.amount, self.currency)
def __mul__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot multiply two {self.__class__.__name__} instances: "
"different currency property")
return Money(self.amount * other.amount, self.currency, self.precision)
def __truediv__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot divide two {self.__class__.__name__} instances: "
"different currency property")
return Money(self.amount / other.amount, self.currency, self.precision)
def __iadd__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot add two {self.__class__.__name__} instances: "
"different currency property")
self.amount += other.amount
return self
def __isub__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot subtract two {self.__class__.__name__} instances: "
"different currency property")
self.amount -= other.amount
return self
def __imul__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot multiply two {self.__class__.__name__} instances: "
"different currency property")
self.amount *= other.amount
return self
def __idiv__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot divide two {self.__class__.__name__} instances: "
"different currency property")
self.amount /= other.amount
return self
def __lt__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot compare two {self.__class__.__name__} instances: "
"different currency property")
return self.amount < other.amount
def __eq__(self, other):
if self.currency != other.currency:
raise ValueError(f"cannot compare two {self.__class__.__name__} instances: "
"different currency property")
return self.amount == other.amount
Testy:
import pytest
from example_shop.shop.money import Currency, Money
from decimal import Decimal
from typing import Optional
def money_euro(amount: str, precision: Optional[int] = None) -> Money:
if precision is None:
return Money(Decimal(amount), Currency("Euro", "EUR", Decimal("4.52")))
return Money(Decimal(amount), Currency("Euro", "EUR", Decimal("4.52")), precision)
def money_usd(amount: str, precision: Optional[int] = None) -> Money:
if precision is None:
return Money(Decimal(amount), Currency("American dollar", "USD", Decimal("4.17")))
return Money(Decimal(amount), Currency("American dollar", "USD", Decimal("4.17")), precision)
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("2.7")),
(money_usd("2.7"), money_usd("1.3"), money_usd("4.0")),
(money_usd("2.700"), money_usd("1.3"), money_usd("4.00000")),
(money_usd("1.5", 4), money_usd("1.5", 3), money_usd("3", 4)),
(money_usd("-1.5", 4), money_usd("3", 5), money_usd("1.5", 5))])
def test_money_add_the_same_currency(price1, price2, expected):
assert price1 + price2 == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("0.3")),
(money_usd("2.7"), money_usd("1.3"), money_usd("1.4")),
(money_usd("2.700"), money_usd("1.3"), money_usd("1.40000")),
(money_usd("1.5", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("3", 5), money_usd("-1.5", 5))])
def test_money_subtract_the_same_currency(price1, price2, expected):
assert price1 - price2 == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("1.8")),
(money_usd("2.7"), money_usd("1.3"), money_usd("3.51")),
(money_usd("2.700"), money_usd("1.3"), money_usd("3.51000")),
(money_usd("0", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("-3", 5), money_usd("-4.5", 5))])
def test_money_multiply_the_same_currency(price1, price2, expected):
assert price1 * price2 == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("1.25")),
(money_usd("2.7"), money_usd("1.3"), money_usd("2.08")),
(money_usd("2.700"), money_usd("1.3"), money_usd("2.08000")),
(money_usd("0", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("-3", 5), money_usd("-0.5", 5))])
def test_money_divide_the_same_currency(price1, price2, expected):
assert price1 / price2 == expected
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_add_the_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 + price2
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_subtract_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 - price2
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_multiply_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 * price2
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_divide_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 / price2
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("2.7")),
(money_usd("2.7"), money_usd("1.3"), money_usd("4.0")),
(money_usd("2.700"), money_usd("1.3"), money_usd("4.00000")),
(money_usd("1.5", 4), money_usd("1.5", 3), money_usd("3", 4)),
(money_usd("-1.5", 4), money_usd("3", 5), money_usd("1.5", 5))])
def test_money_add_in_place_the_same_currency(price1, price2, expected):
result = price1
result += price2
assert result == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("0.3")),
(money_usd("2.7"), money_usd("1.3"), money_usd("1.4")),
(money_usd("2.700"), money_usd("1.3"), money_usd("1.40000")),
(money_usd("1.5", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("3", 5), money_usd("-1.5", 5))])
def test_money_subtract_in_place_the_same_currency(price1, price2, expected):
result = price1
result -= price2
assert result == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("1.8")),
(money_usd("2.7"), money_usd("1.3"), money_usd("3.51")),
(money_usd("2.700"), money_usd("1.3"), money_usd("3.51000")),
(money_usd("0", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("-3", 5), money_usd("-4.5", 5))])
def test_money_multiply_in_place_the_same_currency(price1, price2, expected):
result = price1
result *= price2
assert result == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_usd("1.5"), money_usd("1.2"), money_usd("1.25")),
(money_usd("2.7"), money_usd("1.3"), money_usd("2.08")),
(money_usd("2.700"), money_usd("1.3"), money_usd("2.08000")),
(money_usd("0", 4), money_usd("1.5", 3), money_usd("0", 4)),
(money_usd("1.5", 4), money_usd("-3", 5), money_usd("-0.5", 5))])
def test_money_divide_in_place_the_same_currency(price1, price2, expected):
result = price1
result /= price2
assert result == expected
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_add_in_place_the_different_currency(price1, price2):
with pytest.raises(ValueError):
result = price1
result += price2
assert result
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_subtract_in_place_different_currency(price1, price2):
with pytest.raises(ValueError):
result = price1
result -= price2
assert result
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_multiply_in_place_different_currency(price1, price2):
with pytest.raises(ValueError):
result = price1
result *= price2
assert result
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.5"), money_euro("1.2")),
(money_euro("1.2"), money_usd("1.5")),
(money_usd("1.5", 4), money_euro("1.2", 5)),
(money_euro("1.2", 4), money_usd("1.5", 5))])
def test_money_divide_in_place_different_currency(price1, price2):
with pytest.raises(ValueError):
result = price1
result /= price2
assert result
@pytest.mark.parametrize("price1,price2,expected",
[(money_euro("1.23"), money_euro("4.56"), True),
(money_euro("1.5"), money_euro("1"), False),
(money_usd("-2"), money_usd("0"), True),
(money_euro("0"), money_euro("0"), False)])
def test_less_than_the_same_currency(price1, price2, expected):
assert (price1 < price2) == expected
@pytest.mark.parametrize("price1,price2,expected",
[(money_euro("1.23"), money_euro("4.56"), False),
(money_euro("1.5"), money_euro("1"), False),
(money_usd("-2"), money_usd("0"), False),
(money_euro("0"), money_euro("0"), True)])
def test_equal_the_same_currency(price1, price2, expected):
assert (price1 == price2) == expected
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.23"), money_euro("4.56")),
(money_euro("1.5"), money_usd("1"))])
def test_less_than_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 < price2
@pytest.mark.parametrize("price1,price2",
[(money_usd("1.23"), money_euro("4.56")),
(money_euro("1.5"), money_usd("1"))])
def test_equal_different_currency(price1, price2):
with pytest.raises(ValueError):
assert price1 == price2
Czy uzależnianie od siebie kolejności przypisań w konstruktorze jest ok gdy np. dana właściwość wykorzystuje wcześniejszą zmeinną?
Z góry dziękuję i pozdrawiam :-)