Single Dispatch Generic Functions
Posted April 29, 2021 by clannadzsy ‐ 2 min read
Environment: CPython 3.12.14, standard library only. Save the block as htmlize_demo.py and run python htmlize_demo.py; the commented output shows the expected handlers. Set element order and object representations may vary, so compare the rendered structure rather than relying on their exact printed order.
singledispatch chooses an implementation from the first argument’s runtime type, not from every argument or from a sequence’s item types. Type-annotation registration needs Python 3.7 or newer. The abc.Sequence below comes from collections.abc; it does not use the old collections.Sequence alias, which was removed in Python 3.10.
"""
from Fluent Python 2nd Example 9-19
"""
from functools import singledispatch
from collections import abc
import fractions
import decimal
import html
import numbers
@singledispatch
def htmlize(obj: object) -> str:
content = html.escape(repr(obj))
return f'<pre>{content}</pre>'
@htmlize.register
def _(text: str) -> str:
content = html.escape(text).replace('\n', '<br>\n')
return f'<p>{content}</p>'
@htmlize.register
def _(seq: abc.Sequence) -> str:
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
@htmlize.register
def _(n: numbers.Integral) -> str:
return f'<pre>{n} (0x{n:x})</pre>'
@htmlize.register
def _(n: bool) -> str:
return f'<pre>{n}</pre>'
@htmlize.register(fractions.Fraction)
def _(x) -> str:
frac = fractions.Fraction(x)
return f'<pre>{frac.numerator}/{frac.denominator}</pre>'
@htmlize.register(decimal.Decimal)
@htmlize.register(float)
def _(x) -> str:
frac = fractions.Fraction(x).limit_denominator()
return f'<pre>{x} ({frac.numerator}/{frac.denominator})</pre>'
if __name__ == '__main__':
param_list = [
{1, 2, 3},
abs,
'Hello \n World',
123,
['linux', 1, {1, 2, 3}],
True,
fractions.Fraction(2, 3),
0.1,
decimal.Decimal('0.123456')
]
for p in param_list:
print(p)
print(htmlize(p))
print('\n')
# {1, 2, 3}
# <pre>{1, 2, 3}</pre>
#
#
# <built-in function abs>
# <pre><built-in function abs></pre>
#
#
# Hello
# World
# <p>Hello <br>
# World</p>
#
#
# 123
# <pre>123 (0x7b)</pre>
#
#
# ['linux', 1, {1, 2, 3}]
# <ul>
# <li><p>linux</p></li>
# <li><pre>1 (0x1)</pre></li>
# <li><pre>{1, 2, 3}</pre></li>
# </ul>
#
#
# True
# <pre>True</pre>
#
#
# 2/3
# <pre>2/3</pre>
#
#
# 0.1
# <pre>0.1 (1/10)</pre>
#
#
# 0.123456
# <pre>0.123456 (1929/15625)</pre>
For a quick check, append the following to the same file. It verifies escaping, recursive list dispatch and the separate bool handler (bool is a subclass of int). The decimal/float handler deliberately uses limit_denominator(), so its fraction may be an approximation and need not expose the exact binary representation of a float.
assert htmlize('<&>') == '<p><&></p>'
assert htmlize(True) == '<pre>True</pre>'
assert htmlize(123) == '<pre>123 (0x7b)</pre>'
assert htmlize(['linux', 1]) == (
'<ul>\n<li><p>linux</p></li>\n<li><pre>1 (0x1)</pre></li>\n</ul>'
)