prePEP: Money data type

Alex Martelli aleaxit at yahoo.com
Mon Oct 20 04:47:19 EDT 2003
On Monday 20 October 2003 06:33 am, Ian Bicking wrote:
   ...
> > BECAUSE for reasons totally unclear to me x isn't allowed to carry
> > the rounding spec, even though all computations on x must use that
> > spec, would drive me away in no time.
>
> Money could carry the rounding policy -- I don't really know if that's
> right or not, I'm not convinced either way.  But I'm not sure it's
> really "money" if it carries a rounding policy.  A rounding policy
> isn't intrinsic to the notion of money.  EU has a rounding policy, but

Thanks, you clarified your reasons now, even though I disagree.
Money is a legal and social construct and the laws and customs that
dictate its use will impose a rounding policy (which might well be
"no rounding allowed" in some cases).

> require "v1, v2, v3 = x.divide(3)", so that pennies aren't lost.  I
> don't see how a rounding policy can handle that, unless you intend a

By having among others a "Money.no_rounding_allowed" policy that
can raise an exception on operations that are to be disallowed.  Easy!


> If you include a rounding policy you are really specifying an
> accounting entry.  That "money" cannot be put into other accounts
> without checking and converting the rounding policy.  But the rounding
> policy may not be the only policy -- I don't have experience to know of
> other circumstances, but it's easy enough to imagine other kinds of
> policies that belong to entries.

But class Money in itself should specify *arithmetic*, that's all.  Accounting
may require a bazillion other kinds of policies, but I see as the role of
class Money in that larger picture that of handing arithmetic -- and that
requires a rounding policy (only).  Policies such as "withdrawals of over
10,000 in cash must send an email to account XXX" belong elsewhere,
at higher application levels; arithmetic belongs in class Money.

> If that's common, then you probably will want a formal "entry" object,
> which would contain a money object.  At that point the rounding
> information held by the money object will be redundant and perhaps a
> hinderance.

Not at all: being able to delegate arithmetic issues to the "quantity"
object (Money in this case) is, on the contrary, crucial to simplify
higher application levels.  There IS a case for "money with currency
unit" to be separate from pure Money, basically to catch errors due
to trying to sum Euros and Dollars and the like; but that can be built
by subclassing a Money that's blissfully oblivious to currencies.  As
long as that Money CAN handle arithmetic, though; otherwise, what
ARE its responsibilities?!

> I prefer the idea of wrapping money in domain-specific ways, and
> leaving the money object relatively pure.

A relatively-pure object should generally have an area of responsibility
nevertheless.  I see Money's area of responsibility as arithmetic, and
rounding policy as being intrinsic to that.


> > that we can predict in advance, then the requirement to subclass is not
> > too onerous.  But normal cases should be in the Money class!
>
> Agreed.  The one kind of customization I might consider is if you
> wanted to, application-wide, enforce coding standards, like no implicit
> rounding.  You could subclass and replace the money class for the
> entire application.  It's kind of hackish, but it should actually work
> okay for this particular case.  Most other kinds of customization don't
> work well, because library code will usually use uncustomized classes.

GOOD Library code should accept indications of what class to use (with
a sensible default).  That's a key reason that makes classes as first-class
citizens SUCH a great idea - it's trivially easy to pass them around for
such purposes.  For example, mailbox.UnixMailbox _defaults_ to using
class rfc822.Message, but you can perfectly well (and should, these days)
pass it email.message_from_file (or a factory wrapping the latter if you
need to deal very robustly with potentially malformed mailboxes).

> Especially with immutable instances, where library code will return new
> objects instead of modifying your customized object in place.

Using the passed-in object's __class__ may be sufficient in that case,
and that should often be transparently achievable, as calling methods
on a money instance that return another new money instance should
automatically use the same class.  That's very easy too, e.g. consider
a very toy example, a meant-as-immutable Money that only holds an
amount and lets you sum a number to that returning a new instance:

class Money(object):
    def __init__(self, amount): self.amount = amount
    def __add__(self, other):
        return self.__class__(self.amount + other)

the point is to use self.__class__ as the factory for new objects to be
returned, rather than using Money for the purpose.


> >>>> No, the only places where repr() round trips is for Python literals.
   ...
> > Guaranteed readability is the business of str().  repr() must be
> > complete
> > and accurate even if that sacrifices readability.  Round-tripping
> > cannot
> > generally be guaranteed for many types, but when it can it's a sensible
> > way to double check on completeness and accuracy; readability must
> > not be allowed to interfere with completeness and accuracy.  Sure,
> > pickle
> > is there for those times where round-tripping is too onerous on other
> > grounds, but readability is not one of those grounds -- otherwise what
> > do you think str() is there FOR?
>
> That's not a realistic idea of repr() -- repr() doesn't need to be
> accurate, because it usually isn't!  Anyone who expects repr() to be a
> accurate way to reconstruct objects isn't just wrong, they aren't even
> paying attention.

Or has read the Python library docs about repr:
"""
For many types, this function makes an attempt to return a string that would 
yield an object with the same value when passed to eval().
"""
MANY types; not ALL (obviously), but MANY.  No "only Python literals"
constraint, no readability intent.  As the docs say about str:
"""
The difference with repr(object) is that str(object) does not always attempt 
to return a string that is acceptable to eval(); its goal is to return a 
printable string.
"""

The docs put it a bit too strongly IMHO, because repr also does not
ALWAYS attempt &c.  I think it would be better to weaken str's docs
to "does not EVEN attempt to" -- str just cares about "nicely printable"
strings, not about acceptability to eval.  But even if we weaken this to
str "not even attempting", there IS still an implication that repr DOES
"attempt", which goes well with the "many types ... makes an attempt"
in repr's own documentation.  I also (of course;-) like the way I've put
this in the Nutshell's docs on __repr__ and __str__ (p. 94): essentially,
repr MUST be accurate and unambiguous, and WHEN FEASIBLE a
good way to achieve that is to produce a string acceptable to eval
(the "round-tripping" you're deprecating).


> repr() is generally for debugging purposes.  It's a summary of an
> object where completeness is valued over aesthetics (contrary to
> str()).  It's not necessarily a complete picture of an object.

Nobody ever said repr is NECESSARILY a complete picture -- it
most obviously can't ALWAYS be that, as there are many cases
where that would be patently unfeasible (e.g., a file object...!).

But it's still worth having repr be "complete" when feasible, and
having it "round-trip" with eval can often be done just fine; when it
can, I think it definitely should.


Alex






More information about the Python-list mailing list