Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python, How to extend Decimal class to add helpful methods

I would like to extend the Decimal class to add some helpful methods to it, specially for handling money.

The problem when I go do this:

from decimal import Decimal
class NewDecimal(Decimal):
    def new_str(self):
        return "${}".format(self)

d1 = NewDecimal(1)
print d1.new_str() # prints '$1'

d2 = NewDecimal(2)
d3 = NewDecimal(3)
d5 = d2 + d3
print d5.new_str()   #exception happens here

It throws an exception:

AttributeError: 'Decimal' object has no attribute 'new_str'

This is because of the way Decimal does arithmetic, it always returns a new Decimal object, by literally calling Decimal(new value) at the end of the computation.
Does anyone no a workaround for this other than completely reimplementing all the arithmetic?

like image 694
Joe Avatar asked Aug 09 '13 19:08

Joe


People also ask

How do I increase precision in Python?

Using “%”:- “%” operator is used to format as well as set precision in python. This is similar to “printf” statement in C programming. Using format():- This is yet another way to format the string for setting precision.

How do you keep 2 decimal places in Python?

2️⃣ f-string Thus, you can use f'{value: . 2f}' to return a string representation of the number up to two decimal places.

How do you get 6 decimal places in Python?

Use the round() function to print a float to 6 decimal places, e.g. print(round(my_float, 6)) . The round() function will round the floating-point number to 6 decimal places and will return the result.


2 Answers

You probably don't actually want to do this just to have an extra method for printing Decimal objects in an alternate way. A top-level function or monkeypatched method is a whole lot simpler, and cleaner. Or, alternatively, a Money class that has a Decimal member that it delegates arithmetic to.

But what you want is doable.


To make NewDecimal(1) + NewDecimal(2) return NewDecimal(3), you can just override __add__:

def __add__(self, rhs):
    return NewDecimal(super().__add__(rhs))

And of course you'll want to override __iadd__ as well. And don't forget mul and all the other numeric special methods.

But that still won't help for Decimal(2) + NewDecimal(3). To make that work, you need to define NewDecimal.__radd__. You also need to ensure that NewDecimal.__radd__ will get called instead of Decimal.__add__, but when you're using inheritance, that's easy, because Python has a rule specifically to make this easy:

Note: If the right operand’s type is a subclass of the left operand’s type and that subclass provides the reflected method for the operation, this method will be called before the left operand’s non-reflected method. This behavior allows subclasses to override their ancestors’ operations.


You may want to read the section Implementing the arithmetic operations in the numbers module docs, and the implementation of fractions.Fraction (which was intended to serve as sample code for creating new numeric types, which is why the docs link directly to the source). Your life is easier than Fraction's because you can effectively fall back to Decimal for every operation and then convert (since NewDecimal doesn't have any different numeric behavior from Decimal), but it's worth seeing all the issues, and understanding which ones are and aren't relevant and why.

like image 134
abarnert Avatar answered Oct 13 '22 11:10

abarnert


The quick way to what you want, would be like this:

from decimal import Decimal
class NewDecimal(Decimal):
    def __str__(self):  
        return "${}".format(self)

    def __add__(self,b):
        return NewDecimal( Decimal.__add__(self,b) )


d1 = NewDecimal(1)
print d1 # prints '$1'

d2 = NewDecimal(2)
d3 = NewDecimal(3)
d5 = d2 + d3
print d5

> $5
like image 44
joaoricardo000 Avatar answered Oct 13 '22 13:10

joaoricardo000