Python string formatting with custom classes
Strings in Python have a .format()
method that allows placeholders to be substituted with values:
In this article we will see how you can use custom classes to do cool stuff with .format()
.
Format options
A useful feature of .format()
is that formatting options can be provided:
In the above example, we have formatted a float
type with the .2f
option. Other Python types that support format options include str
, int
and datetime
. A good source for learning about all the possibilities for format options is pyformat.info.
A neat thing about Python is you can create your own types that accept format options by implementing the __format__
method. For example, using pykakasi we can create a class that can render Japanese script in different ways:
Something particularly attractive about this is that it separates copywriting from engineering. Suppose a utility company is writing to all its customers with their predicted spend next year. Copywriters provide the following template:
Your predicted spend next year is ${amount}.
However, they also stipulate that if amount
is greater than $100, it should be rounded to the nearest dollar. Instead of writing custom code to deal with this situation, the engineering organisation could inform the copywriting team that from now on, they can stipulate this requirement in the following way:
Your predicted spend next year is ${amount:rnd_if_gt_100}.
This is implemented as follows:
This reduces the need for coordination between engineers and copywriters and empowers the copywriters with finer control over how text is displayed.
Missing values
When you format a string, all values must be provided or a KeyError
is raised:
>>> '{a} {b}'.format(a=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'b'
Oftentimes this is desirable, but what if you want to allow a string to be only “partly” formatted? We can use a subclass of dict
:
Notice that we have used format_map
instead of format
. The former accepts a dictionary as the argument whereas the latter requires the dictionary to be unpacked. For a dict
object, the following are equivalent:
vals = {"a": 1}
"{a}".format(**vals)
"{a}".format_map(vals)
However, when vals
is a not a dict
but a subtype of dict
, we need to use format_map
because unpacking using **
causes type information to be lost.
Using every value
Whereas not supplying every parameter causes string formatting to fail, supplying too many parameters is fine:
>>> "{a}".format(a=1, b=2)
'1'
How can we detect this case and raise an exception? Again, using a subtype of dict
:
Conclusion
Python formatting is a great tool, and by combining it with custom classes it solves a wide array of problems. In this article we have seen a few examples of this.