A shipping service mixes measurements from many sources: "30 cm" from one form, (12, "in") from another, a bare number from a third. Each class should say once which unit each attribute is kept in, and every assignment should be converted to that unit automatically.
The setup defines the table UNITS, mapping each dimension ("length", "mass", "time") to its units and their size in the base unit (UNITS["length"]["cm"] == 0.01 metres). Write a descriptor class Quantity(dimension, unit), used like this:
class Parcel:
length = Quantity("length", "cm")
weight = Quantity("mass", "kg")
- Creating a
Quantitywhose unit is not a unit of its dimension (or whose dimension is unknown) raisesValueError. - An assigned value may be a number (
intorfloat, notbool), taken to be in the attribute's own unit; a string made of a number, whitespace and a unit, such as"2.5 ft"; or a tuple(number, unit). Strings and tuples are converted from their unit to the attribute's unit. - A unit of another dimension, an unknown unit, a string that is not of that form, or a negative amount raises
ValueError; a value of any other type raisesTypeError. Every such message starts with the attribute's name, and a refused assignment keeps the previous value. - Reading the attribute gives a
floatin the attribute's unit. Reading it on the class gives the descriptor, which has the attributesname,dimensionandunit.
The tests build classes with your descriptor in setup helpers you can call with Run: parcel_class(), run_class(), parcels(rows), runs(rows), described(), keep_on_error() and random_parcels(n, seed). outcome(call) returns a result or the exception's name and the first word of its message.
Examples
Input: parcels([(30, "0.2 m", (4, "in"), "1500 g"), (10, 10, "1 km", 1), (10, 10, 10, "2 m"), (10, 10, 10, [1, "kg"])])
Output: [(30.0, 20.0, 10.16, 1.5, 6.096), (10.0, 10.0, 100000.0, 1.0, 10000.0), ("ValueError", "weight"), ("TypeError", "weight")]
Input: runs([("10 km", "50 min"), ((5, "mi"), "0.75 h")])
Output: [(10.0, 50.0, 5.0), (8.04672, 45.0, 5.592341997)]
Constraints
- Floats are compared with a relative tolerance of
1e-6. - Parse a string by splitting it on whitespace into exactly two parts; the number part is anything
float()accepts.
Goals
- Write a parameterised descriptor that converts values as they are assigned
- Accept several input forms (a number, a string, a pair) and refuse the rest with clear errors
- Use the descriptor on several classes with different units for the same dimension