This is not a good application of a class. You will never have all three members to initialise the class, so they all have to be marked Optional
. Your class does nothing to stop someone from making an "overdetermined" instance where all three members are set and one of them is wrong. And once you do have an instance, you'll have three different members where you only care about two, and three different methods when you only care about one.
Don't round()
in the middle of calculations. Only do that upon presentation, as implied by the decimal precision field in a formatting call.
A function that fills in the gap could look like
from decimal import Decimal
from typing import Optional
def solve_density(
density: Optional[Decimal] = None,
volume: Optional[Decimal] = None,
mass: Optional[Decimal] = None,
) -> Decimal:
# density = mass / volume
if density is None:
return mass / volume
if volume is None:
return mass / density
if mass is None:
return density * volume
def test() -> None:
volume = solve_density(density=Decimal('0.02'), mass=Decimal('0.1'))
print(f'volume = {volume:.2f}')
if __name__ == '__main__':
test()
This still has the over-determined problem. It doesn't offer any advantages over just having three separate, purpose-built functions, each accepting only two parameters.
There is a very different approach that:
- requires installing and importing Sympy;
- does not require that you re-express the equation for every output;
- is a slightly better use-case for a class;
- captures some of the physical constraints of your variables - they're non-negative, real, and finite; and
- is overkill for some applications but convenient for others:
Define a class that holds a dictionary of symbols, and the equation in question. At solution time, call a single method passing in your knowns as kwargs and getting back your unknown as a float.
This has a certain amount of in-built validation - it will bomb out if you pass unknown variables, the wrong number of variables, or variables that produce a mathematical domain problem.
from sympy import Equality, solve, Symbol
def real(name: str) -> Symbol:
return Symbol(name=name, real=True, finite=True, nonnegative=True)
class DensitySystem:
def __init__(self) -> None:
p = real('p')
m = real('m')
v = real('v')
self.symbols = {s.name: s for s in (p, m, v)}
self.equation = Equality(p, m*v)
def solve(self, **kwargs: float) -> float:
unknown, = self.symbols.keys() - kwargs.keys()
solved, = solve(self.equation, self.symbols[unknown])
value = solved.subs({
self.symbols[k]: known
for k, known in kwargs.items()
})
return float(value)
def test() -> None:
system = DensitySystem()
v = system.solve(p=1, m=2)
print(f'v={v:.2f}')
if __name__ == '__main__':
test()
pm
,mv
orpv
, and return aPmv
. \$\endgroup\$