Python 中 Decimal 的子类化

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不引发异常的情况下编写而不是编写。

我有几个问题:

  1. 我的决定会给我带来任何麻烦吗?

  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 已经非常慢)。

  1. 当cDecimal出现时,这个子类化会起作用吗?

Roa*_*ich 5

目前,它根本不会做你想做的事。您不能将 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)