title: nice decimal.Decimal autonormalization
body: <p>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.</p>
  
  <p>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.</p>
  
  <p>My question is: Is there a cleaner (and faster) way of doing this?</p>
  
  <pre><code>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)
  </code></pre>
  
  <p>Here is how this works:</p>
  
  <pre><code>&gt;&gt;&gt; n
  Decimal('-5423.542')
  &gt;&gt;&gt; type(n)
  &lt;class '__main__.MyDecimal'&gt;
  &gt;&gt;&gt; str(n)
  '-5423,542'
  &gt;&gt;&gt; x = MyDecimal('542.63') / MyDecimal('5.2331')
  &gt;&gt;&gt; x
  Decimal('103.6918843515315969501824922')
  &gt;&gt;&gt; type(x)
  &lt;class '__main__.MyDecimal'&gt;
  &gt;&gt;&gt; y = MyDecimal('5.5252') - MyDecimal('0.0052')
  &gt;&gt;&gt; y
  Decimal('5.52')
  &gt;&gt;&gt; z = decimal.Decimal('5.5252') - decimal.Decimal('0.0052')
  &gt;&gt;&gt; z
  Decimal('5.5200')
  </code></pre>
  
  <p>Thanks in advance!</p>
  
  <p>PS: Credit goes to Anurag Uniyal for his code which gave me a way to start: <a href="https://stackoverflow.com/a/3468410/2334951">https://stackoverflow.com/a/3468410/2334951</a></p>
  
  <p>EDIT1: I came out to redefine as_tuple() method which I could call all the time I need the trimmed Decimal version:</p>
  
  <pre><code>class MyDecimal(decimal.Decimal):
  
      def as_tuple(self):
          sign, digits_, exponent = super().as_tuple()
          digits = list(digits_)
          while exponent &lt; 0 and digits[-1] == 0:
              digits.pop()
              exponent += 1
          while len(digits) &lt;= 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))
  </code></pre>