war*_*iuc 5 python decimal subclassing
我想在我的 Python 程序中使用 Decimal 类进行财务计算。小数不能与浮点数一起使用 - 它们需要首先显式转换为字符串。因此,我决定对 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))
Run Code Online (Sandbox Code Playgroud)
所以现在D(5000000)*D(2.2)我应该能够在D(5000000)*2.2不引发异常的情况下编写而不是编写。
我有几个问题:
我的决定会给我带来任何麻烦吗?
重新实现__mul__在 的情况下不起作用D(5000000)*D(2.2),因为另一个参数的类型是class '__main__.m_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
Run Code Online (Sandbox Code Playgroud)
Decimal 模块期望参数为 Decimal 或 int。这意味着我应该首先将 m_Decimal 对象转换为字符串,然后再转换为 Decimal。但这是很多浪费 - m_Decimal 是 Decimal 的后代 - 我如何使用它来使类更快(Decimal 已经非常慢)。
目前,它根本不会做你想做的事。您不能将 m_decimal 乘以任何值:由于缺少 return 语句,它将始终返回 None :
def __mul__ ( self, other ) :
print (type(other))
return Decimal.__mul__ ( self, other )
Run Code Online (Sandbox Code Playgroud)
即使添加了返回值,您仍然无法执行 D(500000)*2.2,因为浮点数仍然需要在 Decimal 之前转换为 Decimal。mul会接受它。另外, repr 在这里也不合适:
>>> repr(2.1)
'2.1000000000000001'
>>> str(2.1)
'2.1'
Run Code Online (Sandbox Code Playgroud)
我要做的方法是从 float 中创建一个类方法
@classmethod
def fromfloat(cls, f):
return cls(str(f))
Run Code Online (Sandbox Code Playgroud)
然后重写 mul 方法来检查 other 的类型,如果它是浮点数,则对其运行 m_Decimal.fromfloat() :
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)
Run Code Online (Sandbox Code Playgroud)
然后它将完全按照您的预期工作。我个人不会重写新方法,因为对我来说使用 fromfloat() 方法似乎更干净。但那只是我的个人意见。
就像 Dirk 所说,你不需要担心转换,因为 isinstance 可以与子类一起使用。您可能遇到的唯一问题是 Decimal*m_Decimal 将返回 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'
Run Code Online (Sandbox Code Playgroud)
有两种方法可以解决这个问题。首先是向 m_Decimal 的 mul 魔法方法添加显式转换:
def __mul__(self, other):
if isinstance(other, float):
other = m_Decimal.fromfloat(other)
return m_Decimal(Decimal.__mul__(self,other))
Run Code Online (Sandbox Code Playgroud)
另一种方法(我可能不会推荐)是“Monkeypatch”十进制模块:
decimal._Decimal = decimal.Decimal
decimal.Decimal = m_Decimal
Run Code Online (Sandbox Code Playgroud)