Python Programming, news on the Voidspace Python Projects and all things techie.

Subclassing Decimal

emoticon:newspaper I'm part way through creating a library of 'currency units' for use in Resolver One. I thought a good way to go would be to subclass Decimal [1], since you really don't want to be using floating point numbers for financial data (which basically all spreadsheets do of course).

Note

You can see how far I've got with this, which isn't very far as it is a 'lunch-hour-project' for Resolver Hacks, over at the Google Code Project Page and Repository. However, if you read the rest of this entry you will see that most of the implementation may well change dramatically a check-in or two down the line...

So, I create a Currency class that inherits from Decimal and implements sensible rules - like you can't add currencies [2] of different types together and can only multiply amounts of currencies by integers, longs or decimals.

A simple implementation of addition then looks something like this:

def __add__(self, other, context=context): # context used by Decimal
    if type(self) != type(other):
        raise TypeError("can't add different currencies")
    result = Decimal.__add__(self, other, context=context)
    return self.__class__(result)

That seems to work fine. So I add the equality method. If the two values being compared are of different types then I can just return False, otherwise I can defer to Decimal comparison.

def __eq__(self, other):
    if type(self) != type(other):
        return False
    return Decimal.__eq__(self, other)

Does this work? Well, if it did I guess this would be an even duller blog entry. The result of the test self.assertEquals(Pound(1), Pound(1)) is an exception in the addition method complaining that I'm trying to add currencies of different types... Surprised

The reason for this is that Decimal.__eq__ delegates to its __cmp__ method, which first tests for a few special cases (NaN, INF, 0 etc), then coerces other into a Decimal (so that you can compare with integers and longs) and then proceeds to add minus other to self and see if the result is zero. So an equality comparison with a Decimal involves an addition with the right hand value coerced to a Decimal - and bang my nasty 'ole type checks in the Currency addition operator blow up. (decimal.py does a lot of weird stuff which I guess you need to understand the context sufficiently to have a hope of grasping.)

So one solution is to coerce all the values into decimals first, so the addition happens in Decimal rather than in Currency:

def __add__(self, other, context=context):
    if type(self) != type(other):
        raise TypeError("can't add different currencies")
    result = Decimal.__add__(Decimal(self), Decimal(other), context=context)
    return self.__class__(result)

Yuck, so for every addition we pay the extra cost of construction two extra decimals (we could save one by turning the result into a currency unit with result.__class__ = self.__class__ - but I think that brings too much bad karma).

Of course subclassing is really just a form of delegation. I'm layering the currency semantics onto the numerical semantics and delegating to Decimal to actually provide the numerical semantics.

So I could avoid the subclassing altogether, and just have my currency units store a real decimal value away - delegating in the numeric operators. This makes the code look something like this [3]:

class Pounds(object):

    def __init__(self, value):
        # should probably be in __new__
        self.value = Decimal(value)

    def __add__(self, other):
        if type(self) != type(other):
            raise TypeError("can't add different currencies")
        result = self.value + other.value
        return self.__class__(result)

This is a lot more readable. The bugbear is that Resolver One unfortunately does have some 'is-a' tests (isinstance), rather than 'has-a' (duck-typing), for how it handles values, both in terms of displaying the values properly (right aligning numeric values in the grid) and how it treats them in operations like SUM. So the best answer is probably to do both, delegate but also inherit from Decimal. This negates one of the nice results of delegation rather than subclassing, in that I could have only implemented the operators that made sense (does a currency unit really need to support __divmod__ for example). With sub-classing I have to implement all the methods to avoid any of the base class methods being called 'accidentally'.

Oh well, implementing them all isn't really that much work and it's fun to mess around with decimal anyway. And of course as I develop it TDD (writing tests before writing new code) it is hard to mess up things without realising it. On the other hand, a delegation implementation would make it easier to swap in the .NET Decimal under the hood.

Although I've been developing this for IronPython, so far it's all pure Python so I've been running the tests with CPython. It's been nice to be able to use the new testing tool built into the Wing IDE

[1]From the Python decimal standard library module, although arguably on IronPython I would be better of with the .NET Decimal for performance reasons.
[2]Auto-conversion using a table populated by looking up the rate from a web-service would be nice, and easy to add. You probably don't want implicit conversion happening though.
[3]Of course I don't implement the concrete currency classes directly like this, but have a class factory for different currencies.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2008-07-07 23:06:45 | |

Categories: , , Tags: , ,


Diagramming on the Mac

emoticon:podcast One of the annoying things about writing a book is having to create my own diagrams. This was something I wasn't expecting when I started the project, I'm a good writer but awful at producing diagrams.

Thankfully a colleague, Jonathan Hartley, stepped up and helped me.

Here's one of my original diagrams, a 'hedgehog diagram' I produced with 'Paint' (I was still running Windows at home at the time - later I upgraded to Paint.NET which is a much better program but didn't improve my skills):

A hedgehog diagram of basic function syntax in Python

Here is Jonathan's rendering of the same diagram:

A better diagram of basic function syntax in Python

To produce them, he used Open Office Draw. I'm now working on chapter 15 (Embedding IronPython in C# and VB.NET using the DLR Hosting API), and thought I'd give it a try myself.

I used NeoOffice, which is a Mac port of Open Office, and it looks very good. I did try Inkscape, even upgrading my X11 install to the latest version of XQuartz, but it just refuses to run.

Here's, my first attempt:

The DLR hosting API for embedding the IronPython Engine

It's certainly better than my earlier attempts, but I think it still needs some magic from Jonathan.

Several people also recommended OmniGraffle, which looks good, but is not cheap and isn't cross-platform. Given my skill level I think OO offers me everything I need.

Whilst we're on the subject of Mac software, I've also been using a few new programs recently.

  • Pixelmator

    Having created the diagram in Neo Office, I used Pixelmator to edit the Tiff graphics file. I think I got Pixelmator included with one of the recent MacHeist bundles. It seems like a very capable program for basic image editing.

  • MPlayer

    Yet another Open Source video player. I've been trying to play some high quality mkv (Matroska) files encoded with H.264. Neither Quicktime nor VLC (usually excellent) could play it. MPlayer isn't as polished as VLC, but plays them fine.

  • Xee

    Nice little program for image viewing. Much nicer than Preview (which is part of Mac OS X and great for PDFs).

  • Cornerstone

    A shiny commercial Subversion front end. I'm trying out the demo version. It seems great so far. I also tried Versions (also in Beta), but it doesn't let you work with existing working directories (you have to checkout through the Versions UI) - so I didn't get very far.

  • Transmit

    Nice FTP, SFTP (etc) client for the Mac. Again, commercial but worth it. I couldn't find another client that had a '2-pane' UI, except for FileZilla which just refuses to work on my computer. It dies with an odd error [1] that few other people seem to have, and the fixes suggested for them doesn't work for me. Sad

  • Octave Engine Casual

    A new and very funky physics engine from a Japanese developer. Absolutely pointless, but very fun - and very slick on the Mac (and Windows).

  • Chmox

    A CHM reader. The CHM (Compiled Help Manual) format for documentation is popular on Windows, and with reason as if well done it can make for very usable docs. Chmox hasn't been updated for a while, but seems to work fine.

[1]fzsftp could not be started. fzsftp is in the Filezilla package and I tried setting the suggested environment variable. Oh well.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2008-07-06 17:25:21 | |

Categories: , , , Tags: , , , , ,


Hosted by Webfaction

Counter...