数量(包括货币)的单位安全计算
项目描述
包数量为单位安全计算提供了数量,包括货币。
定义数量类
仅通过子类Quantity来声明基本类型的数量:
>>> class Length(Quantity):
... pass
...
但是,只要没有为该类定义单位,就不能为新数量类创建任何实例:
>>> l = Length(1)
Traceback (most recent call last):
ValueError: A unit must be given.
如果有一个参考单位,定义它的最简单方法是给它一个名称和一个符号作为关键字。然后Quantity的元类将自动创建一个单位:
>>> class Mass(Quantity,
... ref_unit_name='Kilogram',
... ref_unit_symbol='kg'):
... pass
...
>>> Mass.ref_unit
Unit('kg')
>>> class Length(Quantity,
... ref_unit_name='Metre',
... ref_unit_symbol='m'):
... pass
...
>>> Length.ref_unit
Unit('m')
现在,可以给定这个单位来创建一个数量:
>>> METRE = Length.ref_unit
>>> print(Length(15, METRE))
15 m
如果没有给出单位,则使用参考单位:
>>> print(Length(15))
15 m
其他单位可以从参考单位(或另一个单位)导出,通过将比例因子乘以该单位给出定义:
>>> a_thousandth = Decimal("0.001")
>>> KILOGRAM = Mass.ref_unit
>>> GRAM = Mass.new_unit('g', 'Gram', a_thousandth * KILOGRAM)
>>> MILLIMETRE = Length.new_unit('mm', 'Millimetre', a_thousandth * METRE)
>>> MILLIMETRE
Unit('mm')
>>> KILOMETRE = Length.new_unit('km', 'Kilometre', 1000 * METRE)
>>> KILOMETRE
Unit('km')
>>> CENTIMETRE = Length.new_unit('cm', 'Centimetre', 10 * MILLIMETRE)
>>> CENTIMETRE
Unit('cm')
可以使用 SI 前缀代替数字作为比例因子。SI 前缀在子模块中提供:
>>> from quantity.si_prefixes import *
>>> NANO.abbr, NANO.name, NANO.factor
('n', 'Nano', Decimal('0.000000001'))
>>> NANOMETRE = Length.new_unit('nm', 'Nanometre', NANO * METRE)
>>> NANOMETRE
Unit('nm')
只有当这些单位具有相同的比例时,才能使用一个单位作为参考并通过给出比例因子来定义所有其他单位。否则,单元可以在不给出定义的情况下被实例化:
>>> class Temperature(Quantity):
... pass
...
>>> CELSIUS = Temperature.new_unit('°C', 'Degree Celsius')
>>> FAHRENHEIT = Temperature.new_unit('°F', 'Degree Fahrenheit')
>>> KELVIN = Temperature.new_unit('K', 'Kelvin')
通过给出基于更基本数量类型的定义来声明数量的派生类型:
>>> class Volume(Quantity,
... define_as=Length ** 3,
... ref_unit_name='Cubic Metre'):
... pass
...
>>> class Duration(Quantity,
... ref_unit_name='Second',
... ref_unit_symbol='s'):
... pass
...
>>> class Velocity(Quantity,
... define_as=Length / Duration,
... ref_unit_name='Metre per Second'):
... pass
...
如果在类声明中没有给出参考单位的符号,则从定义中生成一个符号,只要该定义中的所有类型的量都有一个参考单位。
>>> Volume.ref_unit.symbol
'm³'
>>> Velocity.ref_unit.symbol
'm/s'
其他单位必须明确定义。这可以如上所示或通过从基本数量的单位推导出来完成:
>>> CUBIC_CENTIMETRE = Volume.derive_unit_from(CENTIMETRE,
... name='Cubic Centimetre')
>>> CUBIC_CENTIMETRE
Unit('cm³')
>>> HOUR = Duration.new_unit('h', 'Hour', 3600 * Duration.ref_unit)
>>> KILOMETRE_PER_HOUR = Velocity.derive_unit_from(KILOMETRE, HOUR)
>>> KILOMETRE_PER_HOUR
Unit('km/h')
实例化数量
创建类Quantity子类的实例的最简单方法是调用给出数量和单位的类。如果省略单位,则使用数量的参考单位(如果定义了单位):
>>> Length(15, MILLIMETRE)
Length(Decimal(15), Unit('mm'))
或者,可以将数量和单位相乘:
>>> 17.5 * KILOMETRE
Length(Decimal('17.5'), Unit('km'))
此外,可以从字符串表示创建Quantity子类实例:
>>> Length('17.5 km')
Length(Decimal('17.5'), Unit('km'))
单元安全计算
通过调用 Quantity.convert 方法,可以将数量转换为使用不同单位的数量:
>>> l5cm = Length(Decimal(5), CENTIMETRE)
>>> l5cm.convert(MILLIMETRE)
Length(Decimal(50), Unit('mm'))
>>> l5cm.convert(KILOMETRE)
Length(Decimal('0.00005'), Unit('km'))
可以使用为数字定义的所有比较运算符将数量与其他数量进行比较。只要它们兼容,就会自动考虑不同的单位,即可以进行转换:
>>> Length(27) <= Length(91)
True
>>> Length(27, METRE) <= Length(91, CENTIMETRE)
False
数量可以添加到其他数量或从其他数量中减去……:
>>> Length(27) + Length(9)
Length(Decimal(36))
>>> Length(27) - Length(91)
Length(Decimal(-64))
>>> Length(27) + Length(12, CENTIMETER)
Length(Decimal('27.12'))
>>> Length(12, CENTIMETER) + Length(17, METER)
Length(Decimal('1712'), Length.Unit('cm'))
…只要它们是相同数量类型的实例:
>>> Length(27) + Duration(9)
Traceback (most recent call last):
IncompatibleUnitsError: Can't add a 'Length' and a 'Duration'
数量可以乘以或除以标量,保留单位:
>>> 7.5 * Length(3, CENTIMETRE)
Length(Decimal('22.5'), Unit('cm'))
>>> Duration(66, MINUTE) / 11
Duration(Decimal(6), Unit('min'))
数量可以乘以或除以其他数量……:
>>> Length(15, METRE) / Duration(3, SECOND)
Velocity(Decimal(5))
……只要定义了结果类型的数量……:
>>> Duration(4, SECOND) * Length(7)
Traceback (most recent call last):
UndefinedResultError: Undefined result: Duration * Length
…或者结果是一个标量:
>>> Duration(2, MINUTE) / Duration(50, SECOND)
Decimal('2.4')
钱
金钱是一种特殊的数量。它的单位类型称为货币。
货币与物理量的区别主要体现在两个方面:
-
金额是离散的。对于每种货币,都有一个不能进一步拆分的最小部分。
-
不同货币之间的关系不是固定的,而是随时间变化的。
子包quantity.money提供了处理这些细节的类和函数。
货币必须明确注册为单位以供进一步使用。最简单的方法是调用Money.register_currency。该方法由 ISO 4217 中定义的货币数据库支持。它将 3 个字符的 ISO 4217 代码作为参数。
Money派生自Quantity,因此所有对数量的操作也可以应用于Money的实例。但由于货币之间没有固定的关系,不同货币的金额之间没有隐含的转换。结果值总是量化为用货币定义的最小分数。
可以使用类ExchangeRate定义两种货币之间的转换因子。它给出了单位货币(又名基础货币)、单位倍数、期限货币(又名价格货币)和期限金额,即期限货币金额等于单位货币单位倍数。
将某种货币的金额乘以与单位货币相同的货币的汇率会得到等值的期限货币。同样,将某种货币的金额除以与期限货币相同的货币的汇率会产生等值的单位货币金额。
由于Money从Quantity派生,它可以与其他数量组合以定义新数量。例如,这对于定义每份的价格很有用。
有关更多详细信息,请参阅源代码分发或此处提供的文档。