Подкласс десятичный в Python - PullRequest
2 голосов
/ 11 января 2010

Я хочу использовать десятичный класс в моей программе на Python для финансовых расчетов. Десятичные дроби не работают с числами с плавающей точкой - им нужно сначала явное преобразование в строки. Поэтому я решил создать подкласс Decimal, чтобы иметь возможность работать с плавающими без явных преобразований.

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))

Так что теперь вместо записи D(5000000)*D(2.2) я должен иметь возможность писать D(5000000)*2.2 без исключения исключений.

У меня есть несколько вопросов:

  1. Мое решение вызовет у меня проблемы?

  2. Переопределение __mul__ не работает в случае D(5000000)*D(2.2), потому что другой аргумент имеет тип class '__main__.m_Decimal', но вы можете увидеть в десятичном модуле это:

decimal.py, строка 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

Десятичный модуль ожидает, что аргумент будет Decimal или int. Это означает, что я должен сначала преобразовать мой объект m_Decimal в строку, затем в десятичную. Но это большая трата - m_Decimal является потомком Decimal - как я могу использовать это, чтобы сделать класс быстрее (Decimal уже очень медленный).

  1. Когда появится cDecimal, будет ли работать этот подкласс?

Ответы [ 3 ]

3 голосов
/ 12 января 2010

В настоящее время он не будет делать то, что вы хотите вообще. Вы не можете умножить ваш m_decimal ни на что: он всегда будет возвращать None из-за пропущенного оператора return:

    def __mul__ ( self, other ) :
        print (type(other))
        return Decimal.__mul__ ( self,  other )

Даже с добавленным возвратом вы все равно не можете выполнить D (500000) * 2.2, поскольку число с плавающей запятой все еще нуждается в преобразовании в десятичное число до десятичного числа. Кроме того, repr здесь не подходит:

>>> repr(2.1)
'2.1000000000000001'
>>> str(2.1)
'2.1'

То, как я бы это сделал, это сделать метод класса из плавания

    @classmethod
    def fromfloat(cls, f):
        return cls(str(f))

Затем переопределите метод mul, чтобы проверить тип other, и запустите на нем m_Decimal.fromfloat (), если он является 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)

Тогда он будет работать точно так, как вы ожидаете. Лично я не переопределил бы новый метод, поскольку мне кажется более чистым использовать метод fromfloat (). Но это только мое мнение.

Как сказал Дирк, вам не нужно беспокоиться о преобразовании, поскольку isinstance работает с подклассами. Единственная проблема, с которой вы можете столкнуться, это то, что Decimal * m_Decimal будет возвращать десятичное число, а не ваш подкласс:

>>> 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'

Есть два способа это исправить. Во-первых, нужно добавить явное преобразование к методу магии m_Decimal:

    def __mul__(self, other):
        if isinstance(other, float):
            other = m_Decimal.fromfloat(other)
        return m_Decimal(Decimal.__mul__(self,other))

Другой способ, который я, вероятно, не рекомендовал бы, - это "Monkeypatch" десятичный модуль:

decimal._Decimal = decimal.Decimal
decimal.Decimal = m_Decimal
1 голос
/ 12 января 2010

По моему мнению, вы не должны использовать поплавки вообще. Поплавки не являются подходящим инструментом для финансового приложения. Везде, где вы используете float, вы должны иметь возможность использовать str или Decimal, чтобы гарантировать, что вы не теряете точность.

Например.
Ввод пользователя, Ввод файла - очевидно, используйте str и конвертируйте в десятичную, чтобы выполнить любую математику
База данных - используйте десятичный тип, если он поддерживается, в противном случае используйте строки и преобразуйте их в десятичное.

Если вы настаиваете на использовании float, помните, что python float эквивалентен double на многих других платформах, поэтому, если вы храните их в базе данных, например, убедитесь, что поле базы данных имеет тип double

0 голосов
/ 14 января 2010

Используйте cdecimal или decimal.py из 2.7 или 3.2. Все они имеют from_float метод класса:


class MyDecimal(Decimal):
    def convert(self, v):
        if isinstance(v, float):
            return Decimal.from_float(v)
        else:
            return Decimal(v)
    def __mul__(self, other):
        other = self.convert(other)
        return self.multiply(other)
...