Я хочу использовать десятичный класс в моей программе 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 уже очень медленный).
В настоящее время он не будет делать то, что вы хотите вообще. Вы не можете умножить свой 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
По-моему, вы не должны использовать поплавки. Поплавки не являются подходящим инструментом для финансового приложения. В любом месте, где вы используете поплавки, вы должны использовать str или Decimal, чтобы гарантировать, что вы не теряете точность.
Например.
Пользовательский ввод, ввод файлов - очевидно, использует str и конвертирует в десятичный код для любой математики
База данных - используйте десятичный тип, если он поддерживается, в противном случае используйте строки и конвертируйте в Decimal в ваше приложение.
Если вы настаиваете на использовании поплавков, помните, что python float
эквивалентен double
на многих других платформах, поэтому, если вы храните их в базе данных, например, убедитесь, что поле базы данных имеет тип double
Используйте 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)
isinstance
правильно учитывает подклассы ...isinstance(d, m_Decimal)
подразумеваетisinstance(d, Decimal)
.