【问题标题】:Subclassing Decimal in PythonPython中的小数子类化
【发布时间】:2010-01-11 19:42:31
【问题描述】:

我想在我的 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))

所以现在我应该能够写D(5000000)*2.2 而不是写D(5000000)*D(2.2) 而不会引发异常。

我有几个问题:

  1. 我的决定会给我带来什么麻烦吗?

  2. D(5000000)*D(2.2) 的情况下重新实现 __mul__ 不起作用,因为另一个参数的类型为 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 模块需要参数是 Decimal 或 int。这意味着我应该先将我的 m_Decimal 对象转换为字符串,然后再转换为十进制。但这很浪费 - m_Decimal 是 Decimal 的后代 - 我如何使用它来使课程更快(Decimal 已经很慢了)。

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

【问题讨论】:

  • 请记住,将 Decimal 乘以 float 会导致 Decimal 精度降低,这就是为什么一开始没有实施此操作的原因。
  • 不确定,为什么必须转换? isinstance 正确考虑了子类化... isinstance(d, m_Decimal) 暗示 isinstance(d, Decimal)
  • 你的操作肯定会导致小数的用途出现问题。如果您不需要它们或不了解它们的用途,请不要使用它们;我建议您检查 IEEE 754 标准。

标签: python decimal subclassing


【解决方案1】:

目前,它根本不会做你想做的事。您不能将 m_decimal 乘以任何值:由于缺少 return 语句,它将始终返回 None:

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

即使添加了返回值,您仍然无法执行 D(500000)*2.2,因为浮点数仍需要转换为小数,然后才能接受小数。mul 会接受它。另外,repr 在这里也不合适:

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

我会做的方式是创建一个类方法,fromfloat

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

然后重写mul方法检查other的类型,如果是float则对其运行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)

然后它将完全按照您的预期工作。我个人不会覆盖新方法,因为使用 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'

有两种方法可以解决此问题。首先是对 m_Decimal 的 mul 魔术方法添加显式转换:

    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

【讨论】:

    【解决方案2】:

    在我看来,您根本不应该使用浮点数。浮点数不是金融应用程序的正确工具。在您使用浮点数的任何地方,您都应该能够使用 str 或 Decimal 来确保不会丢失精度。

    例如。
    用户输入、文件输入 - 显然使用 str 并转换为 Decimal 来进行任何数学运算
    数据库 - 如果支持,则使用小数类型,否则使用字符串并在您的应用中转换为小数。

    如果您坚持使用浮点数,请记住 python float 在许多其他平台上等同于 double,因此如果您将它们存储在数据库中,请确保数据库字段的类型为 double

    【讨论】:

    • >在任何使用浮点数的地方都应该能够使用 str 或 Decimal 来确保不会丢失精度。问题是这样的:我正在编写一个支持其他程序员编写的外部模块的平台。我使用 PyQt 和动态生成的表单。我开发了自己的用于编辑小数的小部件,这里出现了关于它保留的值的问题。我想使用小数,但对于用户(程序员)模块,每次都写起来并不直观:myvar = decimalEditWidget.value + decimal.Decimal ('2.2') 所以我想让这个转换透明
    【解决方案3】:

    使用 2.7 或 3.2 中的 cdecimal 或 decimal.py。所有这些都有 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多