monetary applications (et al)

Alex Martelli aleax at aleax.it
Thu Sep 19 09:31:47 CEST 2002


<posted & mailed>

terry wrote:
        ...
> I do admit to being OO inhibited, so maybe you could take it from this
> short piece code and execution results, and, provide a brief
> description of what you would put into this money class that would
> eliminate this error message, while maintaining the code simplicity.
> 
> -------------------------------------------------
> class money:
> def __init__(self,name):
> self.name = name

It doesn't seem to me, from the example use below, that the
argument to money's constructor has anything to do with a
"name" -- rather, you seem to want to use it as an *amount*.
Therefore, call it 'amount', NOT 'name'.

Giving your variables (attributes, functions, classes,
methods, whatever) names that are widely at variance with
the variables' meaning is a horrible practice.  I'm not
sure what this has to do with your "being OO inhibited":
it would be a practice just as despicable in '60s-vintage
Cobol as in the spiffiest 21st-century language.

Then, it seems the money class needs to participate in
arithmetic as a number.  Thus, it clearly needs to define
the special methods that denote it's a number.  All in all:

class Money:
    def __init__(self, amount):
        self.amount = amount
    def __add__(self, other):
        return Money(self.amount + other)
    __radd__ = __add__
    def __mul__(self, other):
        return Money(self.amount * other)
    __rmul__ = __mul__

So far, class Money is just a wrapper over an 'amount' of
unknown type, simply delegating addition and multiplication
to the 'amount' (other arithmetic aspects work much the
same, except of course that they're not all commutative).
It's not hard for it to supply extra functionality that
you haven't requested but probably do want, e.g.:

    def __str__(self):
        return "$%.2f" % self.amount

so that, e.g.,
    print Money(100) * 0.5
would display
    $50.00

This still doesn't do anything particularly interesting
(but the __str__ method would of course in fact use the
locale settings to display monetary amounts in ways more
appropriate to the locale - NOW we're starting to add
some value, at last).  One probably also wants a __repr__:

    def __repr__(self):
        return "Money(%.2f)" % self.amount

More interesting may be doing something substantial
about the underlying arithmetic.  This class we're
sketching does arithmetic just like the underlying
type of amount -- it just pretties things up at output
time to display exactly two decimal digits, but there
is nothing about it that deals with the _accuracy_ of
those digits.  In any real financial application there
are constraints, sometimes legal ones, about what kind
of arithmetic must be computed when the true results
would be a fraction of a cent (or Eurocent or whatever
is the minimum legal fraction of money).

This class doesn't _know_ what the legal restrictions
are (as they'll no doubt differ from country to country,
at least, and more often than not by application area
too) -- but it makes it reasonably easy to insert such
knowledge when you DO have it!  For example, if you
work in an application area and jurisdiction where the
arithmetic must obey the standard rules of decimal
computation, you could simply fix the constructor to:

    def __init__(self, amount):
        import Decimal
        self.amount = Decimal.Decimal(amount)

and everything would fall in line as if by magic, thanks
to polymorphism.  It's pretty easy to customize class
Money to accept the underlying numerical class as a
parameter, too, e.g by inheritance:

    class FlexibleMoney:
        numberclass = float
        def __init__(self, amount):
            self.amount = self.numberclass(amount)
        # the rest is, again, unchanged

so now you could have, depending on jurisdiction and
application area, e.g.:

    import Decimal
    class Money(FlexibleMoney):
        numberclass = Decimal.Decimal

or else, again for example:

    import gmpy
    class Money(FlexibleMoney):
        numberclass = gmpy.mpq

(to respect the rules of unbounded-precision computation
via rational numbers), and so on, and so forth.


> unitprice = money(100)
> qty = 2
> amount = qty * unitprice
> 
> terry at terry:~> python money.py
> Traceback (most recent call last):
>   File "money.py", line 7, in ?
>     amount = qty * unitprice
> TypeError: unsupported operand type(s) for *: 'int' and 'instance'
> -------------------------------------------------

Right -- your class Money didn't supply __rmul__, so the
multiplication you tried to perform couldn't be performed.
The above examples do supply __rmul__, so this will work fine.


> Support for mixed number types in expressions involving this money
> class must come from somewhere - where?  And, it must be capable of

That's the nice thing about polymorphism: it doesn't matter where
the underlying arithmetic functionality is coming from, only that
it respect the rules you want it to respect (depending on legal
jurisdiction and application area).  gmpy wraps the GMP library,
so that's where the functionality comes from; Decimal is pure
Python, and you should import it from Aahz's side and inspect it
to gain some knowledge; and so on, and so forth.

> determing and setting amount to be of class money to be consistent with
> the rest of Python's complexity hierarchy for the handling of numbers.

That's reasonably trivial -- particularly now that (thanks be) you
don't NEED to support coercion any more (though gmpy's current
version still does support it, because it's meant to work with
older versions of Python too -- one day I'll do a "gmpy in a tie"
release and clean it up enormously, I promise:0).


> The rest of the problem is that the above is a trivial calculation.
> What happens to your recommendation when faced with an additional
> number type?

Nothing strange, if your underlying code is sound.

> taxrate = 0.06
> amount = qty * unitprice + (qty*unitprice)*taxrate

Works fine with the trivial class I sketched here, of course.

 
> As I see it, if Python had an intrinsic number type of 'money', the
> logic for determing the results of the above calculation would be
> easily determined and consistent with expectations, as in VB.

You see it wrong.  The law establishes what rules arithmetic must
follow in a lot of hairy cases, and no single intrinsic type can
follow all the rules for every jurisdiction and application, since
the rules DO differ.  In theory, you could go to jail in some
places because of the way you round certain fractions of a cent
(or whatever) in some application -- and the judge should not
accept "but VB does it that way" as covering your responsibility:
an application can be used to compute (e.g.) worker-compensation,
or tax liability, or actual annualized interest rates to be
posted as the law requires (in the EU), etc, etc, only if the
application is certified and carries the needed forms of sworn
affidavits about it.  When you sign a piece of paper swearing that
the application abides by whatever weirdness the law wants it to
abide by, you have to KNOW that it does.  In practice, white collar
crims is generally dealt with very leniently by the courts -- though
that might change in the current climate (but not in Italy, where
the government's main political purpose is ensuring that nobody
can possibly go to jail for financial shehanigans far darker than
these, since the prime minister and his closest associates would
be the prime candidates for any such jail terms if applicable...
so, ME, being an Italian citizen and resident, I'm reasonably
well covered in this regard... US citizens and residents should be
far more wary these days:-).

Note: my knowledge of such issues is second- and third-hand: the
student in financial economics in the family is my son, I'm an
engineer and the computation-constraining laws I know anything
about are rather those about how you're allowed to compute
tensile resistance of reinforced concrete and so forth.  But it
DOES make sense that the law should specify such issues, even
though it may not seem so when you're *not legally allowed* to
do arithmetic the same way for any of three different purposes.


> Thanks for your time,

You're welcome.  Better to SPEND some time considering such
issues in all needed detail, than to SERVE time later because
one didn't, right?-)


Alex




More information about the Python-list mailing list