This gonna be my first question here. I am trying to make a decimal.Decimal child class which mainly differs the parent by making autonormalization on itself and on the results of its callable arguments whose returns Decimal objects. The code below have the concept to decorate all methods of Decimal to return MyDecimal instances (whose trim zeros of the end of their strings by creation) instead of decimal.Decimals. For this, metaclass was used.
However, I feel this code a bit hacky though. Moreover, according to the speed test results, it is also damn slow: 2.5 secs for the decimal.Decimal vs. 16 secs for MyDecimal on my system.
My question is: Is there a cleaner (and faster) way of doing this?
import decimal
class AutoNormalizedDecimal(type):
def __new__(cls, name, bases, local):
local_items = list(local.items())
parent_items = [i for i in bases[0].__dict__.items()
if i[0] not in local.keys()]
for a in local_items + parent_items:
attr_name, attr_value = a[0], a[1]
if callable(attr_value):
local[attr_name] = cls.decorator(attr_value)
return super(AutoNormalizedDecimal, cls).__new__(
cls, name, bases, local)
@classmethod
def decorator(cls, func):
def wrapper_for_new(*args, **kwargs):
new_string = args[1].rstrip('0').rstrip('.')
if not new_string:
new_string = '0'
newargs = (args[0], new_string)
return func(*newargs, **kwargs)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if (isinstance(result, decimal.Decimal)
and not isinstance(result, MyDecimal)):
return MyDecimal(str(result))
return result
if func.__name__ == '__new__':
return wrapper_for_new
return wrapper
class MyDecimal(decimal.Decimal, metaclass=AutoNormalizedDecimal):
def __str__(self):
return decimal.Decimal.__str__(self).replace('.', ',')
n = MyDecimal('-5423.5420000')
def speedtest():
import time
start = time.time()
d = decimal.Decimal('6')
for i in range(1000000):
d += decimal.Decimal(str(i))
print(time.time()-start)
start = time.time()
d = MyDecimal('6')
for i in range(1000000):
d += MyDecimal(str(i))
print(time.time()-start)
Here is how this works:
>>> n
Decimal('-5423.542')
>>> type(n)
<class '__main__.MyDecimal'>
>>> str(n)
'-5423,542'
>>> x = MyDecimal('542.63') / MyDecimal('5.2331')
>>> x
Decimal('103.6918843515315969501824922')
>>> type(x)
<class '__main__.MyDecimal'>
>>> y = MyDecimal('5.5252') - MyDecimal('0.0052')
>>> y
Decimal('5.52')
>>> z = decimal.Decimal('5.5252') - decimal.Decimal('0.0052')
>>> z
Decimal('5.5200')
Thanks in advance!
PS: Credit goes to Anurag Uniyal for his code which gave me a way to start: https://stackoverflow.com/a/3468410/2334951
EDIT1: I came out to redefine as_tuple() method which I could call all the time I need the trimmed Decimal version:
class MyDecimal(decimal.Decimal):
def as_tuple(self):
sign, digits_, exponent = super().as_tuple()
digits = list(digits_)
while exponent < 0 and digits[-1] == 0:
digits.pop()
exponent += 1
while len(digits) <= abs(exponent):
digits.insert(0, 0)
return decimal.DecimalTuple(sign, tuple(digits), exponent)
def __str__(self):
as_tuple = self.as_tuple()
left = ''.join([str(d) for d in as_tuple[1][:as_tuple[2]]])
right = ''.join([str(d) for d in as_tuple[1][as_tuple[2]:]])
return ','.join((left, right))