2. Quantity

Unit-safe computations with quantities.

2.1. Usage

2.1.1. Defining a quantity class

A basic type of quantity is declared just by sub-classing Quantity:

>>> class Length(Quantity):                             
...     pass
...

But, as long as there is no unit defined for that class, you can not create any instance for the new quantity class:

>>> l = Length(1)                                       
Traceback (most recent call last):
ValueError: A unit must be given.

If there is a reference unit, the simplest way to define it is giving a name and a symbol for it as keywords. The meta-class of Quantity will then create a unit automatically:

>>> 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')

Now, this unit can be given to create a quantity:

>>> METRE = Length.ref_unit
>>> print(Length(15, METRE))
15 m

If no unit is given, the reference unit is used:

>>> print(Length(15))
15 m

Other units can be derived from the reference unit (or another unit), giving a definition by multiplying a scaling factor with that unit:

>>> 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')

Instead of a number a SI prefix can be used as scaling factor. SI prefixes are provided in a sub-module:

>>> 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')

Using one unit as a reference and defining all other units by giving a scaling factor is only possible if the units have the same scale. Otherwise, units can just be instantiated without giving a definition:

>>> class Temperature(Quantity):
...     pass
...
>>> CELSIUS = Temperature.new_unit('°C', 'Degree Celsius')
>>> FAHRENHEIT = Temperature.new_unit('°F', 'Degree Fahrenheit')
>>> KELVIN = Temperature.new_unit('K', 'Kelvin')

Derived types of quantities are declared by giving a definition based on more basic types of quantities:

>>> 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
...

If no symbol for the reference unit is given with the class declaration, a symbol is generated from the definition, as long as all types of quantities in that definition have a reference unit.

>>> Volume.ref_unit.symbol
'm³'
>>> Velocity.ref_unit.symbol
'm/s'

Other units have to be defined explicitly. This can be done either as shown above or by deriving them from units of the base quantities:

>>> 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')

In order to define a quantized quantity, the smallest possible fraction (in terms of the reference unit) can be given as quantum:

>>> class DataVolume(Quantity,
...                  ref_unit_name='Byte',
...                  ref_unit_symbol='B',
...                  quantum=Decimal('0.125')):
...     pass
...
>>> DataVolume.quantum
Decimal('0.125')

The method quantum can then be used to retrieve the smallest amount for a unit:

>>> BYTE = DataVolume.ref_unit
>>> BYTE.quantum
Decimal('0.125')
>>> KILOBYTE = DataVolume.new_unit('kB', 'Kilobyte', KILO * BYTE)
>>> KILOBYTE.quantum
Decimal('0.000125')

2.1.2. Instantiating quantities

The simplest way to create an instance of a Quantity subclass is to call the class giving an amount and a unit. If the unit is omitted, the quantity’s reference unit is used (if one is defined):

>>> Length(15, MILLIMETRE)
Length(Decimal(15), Unit('mm'))
>>> Length(15)
Length(Decimal(15))

Alternatively, an amount and a unit can be multiplied:

>>> 17.5 * KILOMETRE
Length(Decimal('17.5'), Unit('km'))

Also, it’s possible to create a Quantity instance from a string representation:

>>> Length('17.5 km')
Length(Decimal('17.5'), Unit('km'))

If a unit is given in addition, the resulting quantity is converted accordingly:

>>> Length('17 m', KILOMETRE)
Length(Decimal('0.017'), Unit('km'))

Instead of calling a subclass, the class Quantity can be used as a factory function …:

>>> Quantity(15, MILLIMETRE)
Length(Decimal(15), Unit('mm'))
>>> Quantity('17.5 km')
Length(Decimal('17.5'), Unit('km'))

… as long as a unit is given:

>>> Quantity(17.5)
Traceback (most recent call last):
QuantityError: A unit must be given.

If the Quantity subclass defines a quantum, the amount of each instance is automatically rounded to this quantum:

>>> DataVolume('1/7', KILOBYTE)
DataVolume(Decimal('0.142875'), Unit('kB'))

2.1.3. Converting between units

A quantity can be converted to a quantity using a different unit by calling the method Quantity.convert():

>>> l5cm = Length(Decimal(5), CENTIMETRE)
>>> l5cm.convert(MILLIMETRE)
Length(Decimal(50), Unit('mm'))
>>> l5cm.convert(KILOMETRE)
Length(Decimal('0.00005'), Unit('km'))

These kinds of conversion are automatically enabled for types of quantities with reference units. For other types of quantities there is no default way of converting between units.

>>> t27c = Temperature(Decimal(27), CELSIUS)
>>> t27c.convert(FAHRENHEIT)
Traceback (most recent call last):
UnitConversionError: Can't convert '°C' to '°F'.

2.1.3.1. Converters

For types of quantities that do not have a reference unit, one or more callables can be registered as converters:

>>> def fahrenheit2celsius(qty, to_unit):
...     if qty.unit is FAHRENHEIT and to_unit is CELSIUS:
...         return (qty.amount - 32) / Decimal('1.8')
...     return None
...
>>> def celsius2fahrenheit(qty, to_unit):
...     if qty.unit is CELSIUS and to_unit is FAHRENHEIT:
...         return qty.amount * Decimal('1.8') + 32
...     return None
...
>>> Temperature.register_converter(fahrenheit2celsius)
>>> Temperature.register_converter(celsius2fahrenheit)
>>> assert list(Temperature.registered_converters()) == \
...     [celsius2fahrenheit, fahrenheit2celsius]
...

For the signature of the callables used as converters see Converter.

>>> t27c.convert(FAHRENHEIT)
Temperature(Decimal('80.6'), Unit('°F'))
>>> t27c.convert(FAHRENHEIT).convert(CELSIUS)
Temperature(Decimal(27), Unit('°C'))

Alternatively, an instance of TableConverter can be created and registered as converter.

The example given above can be implemented as follows:

>>> tconv = TableConverter({(CELSIUS, FAHRENHEIT): (Decimal('1.8'), 32)})
>>> Temperature.register_converter(tconv)
>>> t27c = Temperature(Decimal(27), CELSIUS)
>>> t27c.convert(FAHRENHEIT)
Temperature(Decimal('80.6'), Unit('°F'))

It is suffient to define the conversion in one direction, because a reversed conversion is used automatically:

>>> t27c.convert(FAHRENHEIT).convert(CELSIUS)
Temperature(Decimal(27), Unit('°C'))

2.1.4. Unit-safe computations

2.1.4.1. Comparison

Quantities can be compared to other quantities using all comparison operators defined for numbers:

>>> Length(27) > Length(9)
True
>>> Length(27) >= Length(91)
False
>>> Length(27) < Length(9)
False
>>> Length(27) <= Length(91)
True
>>> Length(27) == Length(27)
True
>>> Length(27) != Length(91)
True

Different units are taken in to account automatically, as long as they are compatible, that means a conversion is available:

>>> Length(27, METRE) <= Length(91, CENTIMETRE)
False
>>> Temperature(20, CELSIUS) > Temperature(20, FAHRENHEIT)
True
>>> Temperature(20, CELSIUS) > Temperature(20, KELVIN)
Traceback (most recent call last):
UnitConversionError: Can't convert 'K' to '°C'.

Testing instances of different quantity types for equality always returns false:

>>> Length(20) == Duration(20)
False
>>> Length(20) != Duration(20)
True

All other comparison operators raise an IncompatibleUnitsError in this case.

2.1.4.2. Addition and subtraction

Quantities can be added to or subtracted from other quantities …:

>>> Length(27) + Length(9)
Length(Decimal(36))
>>> Length(27) - Length(91)
Length(Decimal(-64))

… as long as they are instances of the same quantity type:

>>> Length(27) + Duration(9)
Traceback (most recent call last):
IncompatibleUnitsError: Can't add a 'Length' and a 'Duration'

When quantities with different units are added or subtracted, the values are converted to the unit of the first, if possible …:

>>> Length(27) + Length(12, CENTIMETRE)
Length(Decimal('27.12'))
>>> Length(12, CENTIMETRE) + Length(17, METRE)
Length(Decimal(1712), Unit('cm'))
>>> Temperature(20, CELSIUS) - Temperature(50, FAHRENHEIT)
Temperature(Decimal(10), Unit('°C'))

… but an exception is raised, if not:

>>> Temperature(20, CELSIUS) - Temperature(281, KELVIN)
Traceback (most recent call last):
UnitConversionError: Can't convert 'K' to '°C'.

2.1.4.3. Multiplication and division

Quantities can be multiplied or divided by scalars, preserving the unit:

>>> 7.5 * Length(3, CENTIMETRE)
Length(Decimal('22.5'), Unit('cm'))
>>> SECOND = Duration.ref_unit
>>> MINUTE = Duration.new_unit('min', 'Minute', Decimal(60) * SECOND)
>>> Duration(66, MINUTE) / 11
Duration(Decimal(6), Unit('min'))

Quantities can be multiplied or divided by other quantities …:

>>> Length(15, METRE) / Duration(3, SECOND)
Velocity(Decimal(5))

… as long as the resulting type of quantity is defined …:

>>> Duration(4, SECOND) * Length(7)
Traceback (most recent call last):
UndefinedResultError: Undefined result: Duration * Length
>>> Length(12, KILOMETRE) / Duration(2, MINUTE) / Duration(50, SECOND)
Traceback (most recent call last):
UndefinedResultError: Undefined result: Velocity / Duration
>>> class Acceleration(Quantity,
...                    define_as=Length / Duration ** 2,
...                    ref_unit_name='Metre per Second squared'):
...     pass
...
>>> Length(12, KILOMETRE) / Duration(2, MINUTE) / Duration(50, SECOND)
Acceleration(Decimal(2))

… or the result is a scalar:

>>> Duration(2, MINUTE) / Duration(50, SECOND)
Decimal('2.4')

When cascading operations, all intermediate results have to be defined:

>>> Length(6, KILOMETRE) * Length(13,  METRE) * Length(250, METRE)
Traceback (most recent call last):
UndefinedResultError: Undefined result: Length * Length
>>> class Area(Quantity,
...            define_as=Length ** 2,
...            ref_unit_name='Square Metre'):
...     pass
...
>>> Length(6, KILOMETRE) * Length(13,  METRE) * Length(250, METRE)
Volume(Decimal(19500000))

2.1.4.4. Exponentiation

Quantities can be raised by an exponent, as long as the exponent is an Integral number and the resulting quantity is defined:

>>> (5 * METRE) ** 2
Area(Decimal(25))
>>> (5 * METRE) ** 2.5
Traceback (most recent call last):
TypeError: unsupported operand type(s) for ** or pow(): 'Length' and
    'float'
>>> (5 * METRE) ** -2
Traceback (most recent call last):
UndefinedResultError: Undefined result: Length ** -2

2.1.5. Rounding

The amount of a quantity can be rounded by using the standard round function. It returns a copy of the quanitity, with its amount rounded accordingly:

>>> round(Length(Decimal('17.375'), MILLIMETRE), 1)
Length(Decimal('17.4'), Unit('mm'))

In any case the unit of the resulting quantity will be the same as the unit of the called quantity.

For more advanced cases of rounding the method Quantity.quantize() can round a quantity to any quantum according to any rounding mode:

>>> l = Length('1.7296 km')
>>> l.quantize(Length(1))
Length(Decimal('1.73', 3), Unit('km'))
>>> l.quantize(25 * METRE)
Length(Decimal('1.725'), Unit('km'))
>>> l.quantize(25 * METRE, ROUNDING.ROUND_UP)
Length(Decimal('1.75', 3), Unit('km'))

2.1.6. Apportioning

The method Quantity.allocate() can be used to apportion a quantity according to a sequence of ratios:

>>> m = Mass('10 kg')
>>> ratios = [38, 5, 2, 15]
>>> portions, remainder = m.allocate(ratios)
>>> portions
[Mass(Fraction(19, 3)),
 Mass(Fraction(5, 6)),
 Mass(Fraction(1, 3)),
 Mass(Decimal('2.5', 2))]
>>> remainder
Mass(Decimal(0, 2))

If the quantity is quantized, there can be rounding errors causing a remainder with an amount other than 0:

>>> b = 10 * KILOBYTE
>>> portions, remainder = b.allocate(ratios, disperse_rounding_error=False)
>>> portions
[DataVolume(Decimal('6.333375'), Unit('kB')),
 DataVolume(Decimal('0.833375'), Unit('kB')),
 DataVolume(Decimal('0.333375'), Unit('kB')),
 DataVolume(Decimal('2.5', 6), Unit('kB'))]
>>> remainder
DataVolume(Decimal('-0.000125'), Unit('kB'))

By default the remainder will be dispersed:

>>> portions, remainder = b.allocate(ratios)
>>> portions
[DataVolume(Decimal('6.333375'), Unit('kB')),
 DataVolume(Decimal('0.833375'), Unit('kB')),
 DataVolume(Decimal('0.33325', 6), Unit('kB')),
 DataVolume(Decimal('2.5', 6), Unit('kB'))]
>>> remainder
DataVolume(Decimal(0), Unit('kB'))

As well as of numbers, quantities can be used as ratios (as long as they have compatible units):

>>> CUBIC_METRE = Volume.ref_unit
>>> LITRE = Volume.new_unit('l', 'Litre', MILLI * CUBIC_METRE)
>>> l = 10 * LITRE
>>> ratios = [350 * GRAM, 500 * GRAM, 3 * KILOGRAM, 150 * GRAM]
>>> l.allocate(ratios)
([Volume(Decimal('0.875', 4), Unit('l')),
  Volume(Decimal('1.25', 3), Unit('l')),
  Volume(Decimal('7.5', 2), Unit('l')),
  Volume(Decimal('0.375', 4), Unit('l'))],
 Volume(Decimal(0, 4), Unit('l')))

2.1.7. Formatting as string

Quantity supports the standard str function. It returns a string representation of the quantity’s amount followed by a blank and the quantity’s units symbol.

In addition, Quantity supports the standard format function. The format specifier should use two keys: ‘a’ for the amount and ‘u’ for the unit, where ‘a’ can be followed by a valid format spec for numbers and ‘u’ by a valid format spec for strings. If no format specifier is given, ‘{a} {u}’ is used:

>>> v = Volume('19.36')
>>> format(v)
'19.36 m³'
>>> format(v, '{a:*>10.2f} {u:<3}')
'*****19.36 m³ '

2.2. Types

QuantityClsDefT

Defintion of derived Quantity sub-classes.

alias of Term[QuantityMeta]

UnitDefT

Definition of derived units.

alias of Term[Unit]

AmountUnitTupleT

Tuple of an amount and an optional unit

alias of Tuple[Rational, Optional[Unit]]

BinOpResT

Result of binary operations on quantities / units

alias of Union[Quantity, Rational, Tuple[Rational, Optional[Unit]]]

ConverterT

Type of converters

alias of Callable[[…], Optional[Rational]]

ConvMapT

The central part of internal API.

This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.

alias of Mapping[Tuple[Unit, Unit], Tuple[Rational, Rational]]

ConvSpecIterableT

The central part of internal API.

This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.

alias of Iterable[Tuple[Unit, Unit, Rational, Rational]]

2.3. Classes

class Unit

Unit of measure.

Note

New instances of Unit can not be created directly by calling Unit. Instead, use <Quantity sub-class>.new_unit.

__copy__() Unit

Return self (Unit instances are immutable).

__eq__(other: Any) bool

self == other

__format__(fmt_spec: str = '') str

Convert to string (according to fmt_spec).

fmt_spec must be a valid format spec for strings.

__ge__(other: Any) bool

self >= other

__gt__(other: Any) bool

self > other

__hash__() int

hash(self)

__le__(other: Any) bool

self <= other

__lt__(other: Any) bool

self < other

__mul__(other: int) Quantity
__mul__(other: float) Quantity
__mul__(other: Real) Quantity
__mul__(other: SIPrefix) Quantity
__mul__(other: Unit) AmountUnitTupleT
__mul__(other: Quantity) BinOpResT

self * other

__pow__(exp: Any) Quantity | Rational

self ** exp

__repr__() str

repr(self)

__rmul__(other: int) Quantity
__rmul__(other: float) Quantity
__rmul__(other: Real) Quantity
__rmul__(other: SIPrefix) Quantity
__rmul__(other: Unit) AmountUnitTupleT
__rmul__(other: Quantity) BinOpResT

other * self

__rtruediv__(other: Any) Quantity

other / self

__str__() str

str(self)

__truediv__(other: int) Quantity
__truediv__(other: float) Quantity
__truediv__(other: Real) Quantity
__truediv__(other: Unit) AmountUnitTupleT
__truediv__(other: Quantity) BinOpResT

self / other

is_base_unit() bool

Return True if the unit is not derived from another unit.

is_derived_unit() bool

Return True if the unit is derived from another unit.

is_ref_unit() bool

Return True if the unit is a reference unit.

property definition: UnitDefT

Return the units definition.

property name: str

Return the units name.

If the unit was not given a name, its symbol is returned.

property normalized_definition: UnitDefT

Return the units normalized definition.

property qty_cls: QuantityMeta

Return the Quantity subclass related to the unit.

property quantum: Rational | None

Return the minimum amount of a quantity with the unit as unit.

Returns None if the quantity class related to the unit does not define a quantum.

property symbol: str

Return the units symbol.

The symbol is a unique string representation of the unit.

class QuantityMeta

Meta class allowing to construct Quantity subclasses.

Parameters:
  • name – name of the new quantity type

  • define_as (Optional[QuantityClsDefT]) – definition of the new derived quantity type

  • ref_unit_symbol (Optional[str]) – symbol of the reference unit to be created

  • ref_unit_name (Optional[str]) – name of the reference unit

  • quantum (Optional[Rational]) – minimum absolute amount for an instance of the new quantity type

__init__(name: str, bases: Tuple[type, ...] = (), clsdict=MappingProxyType({}), **kwds: Any)
__mul__(other: QuantityMeta | Term[ClassWithDefinitionMeta]) Term[ClassWithDefinitionMeta]

Return class definition: cls * other.

static __new__(mcs, name: str, bases: Tuple[type, ...] = (), clsdict=MappingProxyType({}), **kwds: Any) QuantityMeta

Create new Quantity (sub-)class.

__rmul__(other: Term[ClassWithDefinitionMeta]) Term[ClassWithDefinitionMeta]

Return class definition: other * cls.

__truediv__(other: QuantityMeta | Term[ClassWithDefinitionMeta]) Term[ClassWithDefinitionMeta]

Return class definition: cls / other.

derive_unit_from(*args: Unit, symbol: str | None = None, name: str | None = None) Unit

Derive a new unit for cls from units of its base quantities.

Parameters:
  • args – iterable of units of the base quantities of the quantity type

  • symbol – symbol of the new unit, generated based on args if not given

  • name – name of the new unit, defaults to symbol if not given

Raises:
  • TypeError – ‘derive_unit_from’ called on a base quantity

  • TypeError – not all members of args are instances of Unit

  • ValueError – number of given base units doesn’t match number of base quantities of cls

  • ValueError – given base units don’t match base quantities

  • TypeErrorsymbol is not a string or None

  • ValueErrorsymbol is empty

get_unit_by_symbol(symbol: str) Unit

Return the unit with symbol symbol.

Parameters:

symbol – symbol to look-up

Returns:

unit with given symbol

Raises:

ValueError – a unit with given symbol is not registered with cls

is_base_cls() bool

Return True if cls is not derived from other class(es).

is_derived_cls() bool

Return True if cls is derived from other class(es).

new_unit(symbol: str, name: str | None = None, define_as: Quantity | UnitDefT | None = None) Unit

Create, register and return a new unit for cls.

Parameters:
  • symbol – symbol of the new unit

  • name – name of the new unit, defaults to symbol if not given

  • define_as – equivalent of the new unit in terms of another unit (usually given by multiplying a scalar or a SI scale and a unit) or a term defining the new unit in terms of other units

Raises:
  • TypeErrorsymbol is not a string or None

  • ValueErrorsymbol is empty

  • ValueError – a unit with the given symbol is already registered

  • TypeErrordefine_as does not match the quantity type

  • ValueError – term given as define_as does not define a unit

register_converter(conv: ConverterT) None

Add converter conv to the list of converters registered in cls.

Does nothing if converter is already registered.

registered_converters() Iterator[ConverterT]

Return an iterator over the converters registered in ‘cls’.

The converts are returned in reversed order of registration.

remove_converter(conv: ConverterT) None

Remove converter conv from the converters registered in cls.

Raises ValueError if the converter is not present.

units() Tuple[Unit, ...]

Return all registered units of cls as tuple.

property definition: Term[ClassWithDefinitionMeta]

Definition of cls.

property normalized_definition: Term[ClassWithDefinitionMeta]

Normalized definition of cls.

property quantum: Rational | None

Return the minimum absolute amount for an instance of cls.

The quantum is the minimum amount (in terms of the reference unit) an instance of cls can take (None if no quantum is defined).

property ref_unit: Unit | None

Return the reference unit of cls, or None if no one is defined.

class Quantity

Base class for all types of quantities.

__abs__() Quantity

abs(self) -> self.Quantity(abs(self.amount), self.unit)

__add__(other: Quantity) Quantity

self + other

__eq__(other: Any) bool

self == other

__format__(fmt_spec: str = '') str

Convert to string (according to format specifier).

The specifier must be a standard format specifier in the form described in PEP 3101. It should use two keys: ‘a’ for self.amount and ‘u’ for self.unit, where ‘a’ can be followed by a valid format spec for numbers and ‘u’ by a valid format spec for strings.

__ge__(other: Any) bool

self >= other

__gt__(other: Any) bool

self > other

__hash__() int

hash(self)

__le__(other: Any) bool

self <= other

__lt__(other: Any) bool

self < other

__mul__(other: int) Quantity
__mul__(other: float) Quantity
__mul__(other: Real) Quantity
__mul__(other: Quantity) BinOpResT
__mul__(other: Unit) BinOpResT

self * other

__neg__() Quantity

-self -> self.Quantity(-self.amount, self.unit)

__pos__() Quantity

+self

__pow__(exp: int) Quantity

self ** exp

__radd__(other: Quantity) Quantity

self + other

__repr__() str

repr(self)

__rmul__(other: Any) BinOpResT

self * other

__round__(n_digits: int = 0) Quantity

Return copy of self with its amount rounded to n_digits.

Parameters:

n_digits – number of fractional digits to be rounded to

Returns:

round(self.amount, n_digits) * self.unit

__rsub__(other: Quantity) Quantity

other - self

__rtruediv__(other: Any) Quantity

other / self

__str__() str

str(self)

__sub__(other: Quantity) Quantity

self - other

__truediv__(other: int) Quantity
__truediv__(other: float) Quantity
__truediv__(other: Real) Quantity
__truediv__(other: Quantity) BinOpResT
__truediv__(other: Unit) BinOpResT

self / other

allocate(ratios: Collection[Rational | Quantity], disperse_rounding_error: bool = True) Tuple[List[Quantity], Quantity]

Apportion self according to ratios.

Parameters:
  • ratios – sequence of values defining the relative amount of the requested portions

  • disperse_rounding_error – determines whether a rounding error (if there is one due to quantization) shall be dispersed

Returns:

(portions of self according to ratios,

remainder = self - sum(portions))

Raises:
  • TypeErrorratios contains elements that can not be added

  • IncompatibleUnitsErrorratios contains quantities that can not be added

convert(to_unit: Unit) Quantity

Return quantity q where q == self and q.unit is to_unit.

Parameters:

to_unit – unit to be converted to

Returns:

quantity equivalent to self, having unit to_unit

Raises:

IncompatibleUnitsErrorself can’t be converted to to_unit.

equiv_amount(unit: Unit) Rational | None

Return amount e so that e * unit == self.

quantize(quant: Quantity, rounding: ROUNDING | None = None) Quantity

Return integer multiple of quant closest to self.

Parameters:
  • quant – quantum to get a multiple from

  • rounding – rounding mode (default: None)

If no rounding mode is given, the current default mode from module decimalfp is used.

Returns:

integer multiple of quant closest to self (according to

rounding mode)

Raises:
  • IncompatibleUnitsErrorquant can not be converted to self.unit

  • TypeErrorquant is not an instance of type(self)

  • TypeError – type(self) has no reference unit

property amount: Rational

Return the numerical part of the quantity.

property unit: Unit

Return the quantity’s unit.

class Converter

Convert a quantity’s amount to the equivalent amount for another unit.

A quantity converter can be any callable with a signature like conv(qty, to_unit) -> number f so that type(qty)(f, to_unit) == qty.

__call__(qty: Quantity, to_unit: Unit) Rational | None

Convert qty’s amount to the equivalent amount for to_unit.

Parameters:
  • qty – quantity to be converted

  • to_unit – unit for equivalent amount

Returns:

factor f so that f * to_unit == qty, or None if no such factor is available

Raises:
__init__()
__new__(**kwargs)
class TableConverter

Bases: Converter

Converter using a conversion table.

Parameters:

conv_table (Mapping or list) – the mapping used to initialize the conversion table

Each item of the conversion table defines a conversion from one unit to another unit and consists of four elements:

  • from_unit (Unit): unit of the quantity to be converted

  • to_unit (Unit): target unit of the conversion

  • factor (Rational): factor to be applied to the quantity’s amount

  • offset (Rational): an amount added after applying the factor

When a Mapping is given as convTable, each key / value pair must map a tuple (from_unit, to_unit) to a tuple (factor, offset).

When a list is given as convTable, each item must be a tuple (from_unit, to_unit, factor, offset).

factor and offset must be set so that for an amount in terms of from_unit the eqivalent amount in terms of to_unit is:

result = amount * factor + offset

An instance of TableConverter can be called with a Quantity sub-class’ instance qty and a Unit sub-class’ instance to_unit as parameters. It looks-up the pair (qty.unit, to_unit) for a factor and an offset and returns the resulting amount according to the formula given above.

If there is no item for the pair (qty.unit, to_unit), it tries to find a reverse mapping by looking-up the pair (to_unit, qty.unit), and, if it finds one, it returns a result by applying a reversed formula:

result = (amount - offset) / factor

That means, for each pair of units it is sufficient to define a conversion in one direction.

An instance of TableConverter can be directly registered as a converter by calling the method Quantity.register_converter.

__init__(conv_table: ConvMapT | ConvSpecIterableT)
__new__(**kwargs)

2.4. Functions

sum(items: Iterable[Any], start: Any | None = None) Any

Return the sum of start (if not None) plus all items in items.

Parameters:
  • items – iterable of numbers or number-like objects (NOT strings)

  • start – starting value to be added (default: None)

Returns:

sum of all elements in items plus the value of start (if not None). When items is empty, returns start, if not None, otherwise 0.

In contrast to the built-in function ‘sum’ this function allows to sum sequences of number-like objects (like quantities) without having to provide a start value.

2.5. Exceptions

exception QuantityError

Raised when a quantity can not be instanciated.

__init__(*args, **kwargs)
__new__(**kwargs)
exception IncompatibleUnitsError

Raised when operands do not have compatible units.

__init__(msg: str, operand1: Any, operand2: Any)
__new__(**kwargs)
exception UndefinedResultError

Raised when operation results in an undefined quantity.

__init__(op: Callable[[Any, Any], Any], operand1: Any, operand2: Any)
__new__(**kwargs)
exception UnitConversionError

Raised when a conversion between two compatible units fails.

__init__(msg: str, from_unit: Any, to_unit: Any)
__new__(**kwargs)