Подкласс десятичный в Python

1

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

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 без ограничения исключений.

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

  • Будет ли мое решение причинять мне какие-либо проблемы?

  • Повторное выполнение __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 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
    Помните, что умножение десятичного числа на число с плавающей точкой приведет к получению десятичного числа с уменьшенной точностью, поэтому в первую очередь эта операция не реализована.
  • 1
    Не уверен, почему вы должны конвертировать? isinstance правильно учитывает подклассы ... isinstance(d, m_Decimal) подразумевает isinstance(d, Decimal) .
Показать ещё 1 комментарий
Теги:
decimal
subclassing

3 ответа

2
Лучший ответ

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

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

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

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

Как я это сделал, это сделать classmethod, fromfloat

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

Затем переопределите метод mul для проверки типа другого и запустите 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 mul magicmethod:

    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

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

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

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

  • 0
    > Везде, где вы используете float, вы должны иметь возможность использовать str или Decimal, чтобы гарантировать, что вы не потеряете точность. Проблема заключается в следующем: я пишу платформу, которая будет поддерживать внешние модули, написанные другими программистами. Я использую PyQt и динамически генерируемые формы. Я разработал свой собственный виджет для редактирования десятичных дробей, и здесь возникает вопрос о его значении. Я хочу использовать десятичные дроби, но для пользовательских (программистских) модулей писать не всегда интуитивно: myvar = decimalEditWidget.value + decimal.Decimal ('2.2') Так что я хотел сделать это преобразование прозрачным
0

Используйте 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)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню