Generic Functions with Single Dispatch

众所周知, Python 不支持函数重载,因此无法创建一个通用函数,针对不同的参数类型作出不同的处理。例如,创建一个函数 htmlize,接受一个任意类型的 Python 对象作为参数,根据不同的参数类型,将其“序列化”为不同的 HTML 格式:

  • 默认使用<pre>标签;
  • str: 将换行符替换为’
    \n’,然后将其放在<p>标签中;
  • int: 同时显示 10 进制和 16 进制,如: 12 (0xC);
  • list: 以 HTML 列表的形式显示,每个元素按其类型分别格式化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> htmlize({1, 2, 3})
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre>&lt;built-in function abs&gt;</pre>'
>>> htmlize('Heimlich & Co.\n- a game')
'<p>Heimlich &amp; Co.<br>\n- a game</p>'
>>> htmlize(42)
'<pre>42 (0x2a)</pre>'
>>> print(htmlize(['alpha', 66, {3, 2, 1}]))
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>

常见的做法是同if/elif/elif结构,针对不同类型,分别调用相应的“序列化”函数。这样做主要的问题是缺乏灵活性和可扩展性。Python 3.4 引入的 functools.singledispatch 装饰器,可以使普通函数,成为通用函数(generic function),达到像 C++ 函数重载的效果。Python 3.3及以下的版本,在 PyPI 上有 singledispatch 包提供相应功能。

于是,其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3 -tt
# -*- coding: utf-8 -*-

from functools import singledispatch
from collections import abc
import numbers
import html

@singledispatch
def htmlize(obj):
return '<pre>{0}</pre>'.format(html.escape(repr(obj)))


@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)


@htmlize.register(numbers.Integer)
def _(num):
return '{0} (0x{0:X})'.format(num)


@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>{0}</li>\n</ul>'.format(inner)

上述例子来自《O’Reilly Fluent Python》, PEP 443singledispatch 有详细描述。