Subclassing Decimal in Python
decimal, python, subclassing
Solution
Currently, it won't do what you want at all. You can't multiply your m_decimal by anything: it will always return None, due to a missing return statement:
def __mul__ ( self, other ) :
print (type(other))
return Decimal.__mul__ ( self, other )
Even with the return added in, you still can't do D(500000)*2.2, as the float still needs converting to a Decimal, before Decimal.mul will accept it. Also, repr is not appropriate here:
>>> repr(2.1)
'2.1000000000000001'
>>> str(2.1)
'2.1'
The way I would do it, is to make a classmethod, fromfloat
@classmethod
def fromfloat(cls, f):
return cls(str(f))
Then override the mul method to check for the type of other, and run m_Decimal.fromfloat() on it if it is a float:
class m_Decimal(Decimal):
@classmethod
def fromfloat(cls, f):
return cls(str(f))
def __mul__(self, other):
if isinstance(other, float):
other = m_Decimal.fromfloat(other)
return Decimal.__mul__(self,other)
It will then work exactly as you expect. I personally wouldn't override the new method, as it seems cleaner to me to use the fromfloat() method. But that's just my opinion.
Like Dirk said, you don't need to worry about conversion, as isinstance works with subclasses. The only problem you might have is that Decimal*m_Decimal will return a Decimal, rather than your subclass:
>>> Decimal(2) * m_Decimal(2) * 2.2
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
Decimal(2) * m_Decimal(2) * 2.2
TypeError: unsupported operand type(s) for *: 'Decimal' and 'float'
There are two ways to fix this. First is to add an explicit conversion to the m_Decimal's mul magicmethod:
def __mul__(self, other):
if isinstance(other, float):
other = m_Decimal.fromfloat(other)
return m_Decimal(Decimal.__mul__(self,other))
The other way, which I probably wouldn't recommend, is to "Monkeypatch" the decimal module:
decimal._Decimal = decimal.Decimal
decimal.Decimal = m_Decimal
Problem
I want to use Decimal class in my Python program for doing financial calculations. Decimals to not work with floats - they need explicit conversion to strings first. So i decided to subclass Decimal to be able to work with floats without explicit conversions. m_Decimal.py: ``` # -*- coding: utf-8 -*- import decimal Decimal = decimal.Decimal def floatCheck ( obj ) : # usually Decimal does not work with floats return repr ( obj ) if isinstance ( obj, float ) else obj # this automatically converts floats to Decimal class m_Decimal ( Decimal ) : __integral = Decimal ( 1 ) def __new__ ( cls, value = 0 ) : return Decimal.__new__ ( cls, floatCheck ( value ) ) def __str__ ( self ) : return str ( self.quantize ( self.__integral ) if self == self.to_integral () else self.normalize () ) # http://docs.python.org/library/decimal.html#decimal-faq def __mul__ ( self, other ) : print (type(other)) Decimal.__mul__ ( self, other ) D = m_Decimal print ( D(5000000)*D(2.2)) ``` So now instead of writing `D(5000000)*D(2.2)` i should be able to write `D(5000000)*2.2` without rasing exceptions. I have several questions: Will my decision cause me any troubles? Reimplementing `__mul__` doesn't work in case of `D(5000000)*D(2.2)`, because the other argument is of type `class '__main__.m_Decimal'`, but you can see in decimal module this: decimal.py, line 5292: ``` def _convert_other(other, raiseit=False): """Convert other to Decimal. Verifies that it's ok to use in an implicit construction. """ if isinstance(other, Decimal): return other if isinstance(other, (int, long)): return Decimal(other) if raiseit: raise TypeError("Unable to convert %s to Decimal" % other) return NotImplemented ``` The decimal module expects argument being Decimal or int. This means i should convert my m_Decimal object to string first, then to Decimal. But this is lot of waste - m_Decimal is descendant of Decimal - how can i use this to make the class faster (Decimal is already very slow). - When cDecimal will appear, will this subclassing work?