from tinytab import fmt_number
fmt_number(1234567.891)'1,234,567.89'
tinytab has three functions for turning a single number into a display string. They share the same rules: the value is the first argument, and every option is a keyword argument.
fmt_number() rounds to a fixed number of decimal places (2 by default) and groups thousands with a separator:
Use decimals= to change the precision:
The thousands separator is a comma by default. Set another with sep_mark=:
fmt_percent() takes a proportion, where 1 means 100%, and shows one decimal place by default:
fmt_currency() adds a currency symbol and uses the right number of decimal places for the currency. US dollars are the default:
Choose another currency with a three-letter code (in any case). Yen are shown without decimal places, and negative amounts put the sign before the symbol:
[
fmt_currency(1234.5, currency="eur"),
fmt_currency(1234.5, currency="JPY"),
fmt_currency(-5, currency="GBP"),
]['€1,234.50', '¥1,234', '-£5.00']
The supported codes are USD, EUR, GBP, and JPY. Any other code is an error, and the message lists the valid ones:
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[8], line 1 ----> 1 fmt_currency(10, currency="XYZ") File ~/py_projects/example-python-package/tinytab/_formats.py:136, in fmt_currency(x, currency) 132 if code not in CURRENCY_SYMBOLS: 133 # List the valid choices in the message, so the user can fix the call without having to 134 # look anything up. (Joining a dict iterates over its keys.) 135 valid = ", ".join(CURRENCY_SYMBOLS) --> 136 raise ValueError(f"Unknown currency {currency!r}. Valid currencies: {valid}.") 138 decimals = 0 if code == "JPY" else 2 139 # Format the absolute value and put the sign in front of the symbol ("-$5.00"). Formatting 140 # the negative number directly would put the sign after it ("$-5.00"). ValueError: Unknown currency 'XYZ'. Valid currencies: USD, EUR, GBP, JPY.
All three functions accept only numbers (integers and floats). Anything else, including a number written as a string, raises a TypeError:
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[9], line 1 ----> 1 fmt_number("10") File ~/py_projects/example-python-package/tinytab/_formats.py:68, in fmt_number(x, decimals, sep_mark) 44 def fmt_number(x: float, *, decimals: int = 2, sep_mark: str = ",") -> str: 45 """Format a number with a fixed number of decimal places and a thousands separator. 46 47 Parameters (...) 66 '1,235' 67 """ ---> 68 _check_number(x) 69 if decimals < 0: 70 raise ValueError(f"`decimals` must be zero or greater, got {decimals!r}.") File ~/py_projects/example-python-package/tinytab/_formats.py:34, in _check_number(x, arg) 28 def _check_number(x: object, arg: str = "x") -> None: 29 # `bool` is a subclass of `int`, but formatting `True` as "1.00" is almost always a mistake 30 if isinstance(x, bool) or not isinstance(x, (int, float)): 31 # The error message names the argument, shows the bad value, and says what was expected 32 # (practices doc, item 21). `!r` formats the value with `repr()`, so a string shows up 33 # with quotes ('10') and can't be mistaken for a number. ---> 34 raise TypeError(f"Expected a number for `{arg}`, got {type(x).__name__}: {x!r}") TypeError: Expected a number for `x`, got str: '10'
True and False are rejected too, even though Python treats them as the integers 1 and 0. Formatting a boolean as “1.00” is almost always a mistake.