透明地使用 str 和 unicode 对象进行 .translate

Transparently doing .translate with str and unicode objects

这是我用来抽象出 unicode 和 str:

之间 .translate 差异的实现
import types
from string import maketrans

def str_translate(txt, inchars, outchars, deletechars):
    if inchars : transtab = maketrans(inchars, outchars)
    else       : transtab = None
    return txt.translate(transtab, deletechars)


def maketrans_u(inchars, outchars, deletechars):
    '''Create a translation table for unicode. We assume that we
    want to map one inchar to one outchar (but the actual unicode.translate function
    is more powerful: it can also map one inchar to a unicode string)
    We assume deletechars and inchars do not overlap (no checking done!)'''
    if inchars : transtab = dict((ord(inchar), ord(outchar)) for inchar, outchar in zip(inchars, outchars))
    else       : transtab = { }
    # Now map the deletechars to None
    for char in deletechars:
        transtab[ord(char)] = None
    return transtab


def unicode_translate(txt, inchars, outchars, deletechars):
    transtab = maketrans_u(inchars, outchars, deletechars)
    return txt.translate(transtab)


def translate(txt, inchars=None, outchars=None, deletechars=None):
    t = type(txt)
    if   t == types.StringType  : return str_translate(txt, inchars, outchars, deletechars)
    elif t == types.UnicodeType : return unicode_translate(txt, inchars, outchars, deletechars)
    else                        : raise Exception('Not supported type %s' % (t))


if __name__ == '__main__' :
    a = 'abc%=def'
    deletechars = '=%'
    print translate(a, deletechars=deletechars)

这里我失去了 unicode.translate 的一些功能(即,将一个字符转换为字符串),但至少我有一个统一的接口,可以用来转换 unicode 和纯字符串,无需关心类型。

我不喜欢的是:

有没有更好的方法实现透明.translate

this implementation relies in checking the type of the string in order to call the right function

嗯,它还能做什么?你想为不同的类型做不同的事情,你不能用 monkeypatch 类型来以点语法 OO 风格来做,那么你怎么能自动调度类型呢?您正在寻找的是外部派遣。 Python 可以在 3.4+ 中执行此操作(仅在第一个参数上调度,而不是像 CLOS 或 Dylan 这样的所有参数......尽管 PyPI 上有多个调度库)在 PyPI 上 singledispatch, and there's a backport 可以回到 2.6 .所以,你可以这样做:

from singledispatch import singledispatch

@singledispatch
def translate(txt, inchars=None, outchars=None, deletechars=None):
    raise Exception('Not supported type %s' % (t))

@translate.register(str)
def translate(txt, inchars=None, outchars=None, deletechars=None):
    return str_translate(txt, inchars, outchars, deletechars)

@translate.register(unicode)
def translate(txt, inchars=None, outchars=None, deletechars=None):
    return unicode_translate(txt, inchars, outchars, deletechars)

另请注意,我只是使用了 strunicode 而不是 types.StringTypetypes.UnicodeType。正如文档所说,这些类型只是别名,并不是真正必要的。他们所做的只是让你的代码不那么向后兼容。 (并且它们无助于与 3.x 的向前兼容;3.0 只是删除了不必要的别名,而不是使 StringTypeUnicodeType 都成为 str 的别名并添加 BytesType…)

如果您不想使用 PyPI 之外的库或自己实现相同的东西,而是想要手动类型切换,您可能需要 isinstance 而不是 type(x) ==


I can not do txt.translate(...) (I must do translate(txt, ...)

没错;你不能 monkeypatch strunicode。但那又怎样?

which means I can not chain function calls like txt[:50].translate(...)

当然可以,但是您可以像 translate(txt[:50], …).rstrip().split(':') 那样链接函数调用。虽然这在 Java 或 Ruby 等 "everything-is-a-method" 语言中可能看起来反惯用语,但在 Python 中完全没问题。特别是因为无论如何在 Python 中链接超过 2 或 3 个调用是非常罕见的。毕竟,split 之后的下一件事必须是 map 调用或理解,而这些不是通过 Python.

中的方法完成的

Here I am losing some of the power of the unicode.translate (namely, translating one character to a string)

是的,这几乎是最低公分母设计中固有的。一些性能损失也是如此。 str.translateunicode.translate 并没有真正做完全相同的事情。前者是基于 table 的翻译,因为当您只有 256 个可能的值时,这是一个很好的优化,但这确实意味着您放弃了一些灵活性和功能。后者是基于字典的翻译,因为 table 将是对 110 万个值的悲观化,但这意味着您获得了一些额外的灵活性和功能。

所以,在这里,你放弃了 str.translate 的性能(特别是因为你必须为每个翻译动态构建 transtab),以及 [=29] 的灵活性=],两全其美。

如果您确实知道 str 字符串的编码(并且它们确实代表文本——毕竟,str.translate 也可用于二进制数据……),您可以改写这个仅 s.decode(encoding).translate(…).encode(encoding)。但是如果你知道编码,你也可以首先使用 unicode 而不是 str


但我认为更好的解决方案可能是以 returns 两个 table 的元组 str 和一个元组的方式来包装 maketrans unicode 的一个字典。然后你可以调用原生 s.translate(*transtab) 来替代 translate.

不幸的是,您不能为此使用 singledispatch,因为任何参数都可能是 None,这意味着我们又回到了显式类型切换。

def maketrans(inchars, outchars, deletechars):
    if isinstance(inchars, str) or isinstance(deletechars, str):
        return maketrans_s(inchars, outchars, deletechars)
    elif isinstance(inchars, unicode) or isinstance(deletechars, unicode):
        return maketrans_u(inchars, outchars, deletechars)
    raise Exception('Not supported type %s' % (t))

def maketrans_s(inchars, outchars, deletechars):
    if inchars: transtab = maketrans(inchars, outchars)
    else: transtab = None
    return transtab, deletechars

def maketrans_u(inchars, outchars, deletechars):
    # The if was unnecessary here; if inchars is empty, the zip
    # will be too, so you'll get {} as the result. Also notice
    # no ord(outchar); this means you _can_ use Unicode strings
    # when you know the string is Unicode.
    transtab = dict((ord(inchar), outchar) for inchar, outchar in zip(inchars, outchars))
    for char in deletechars:
        transtab[ord(char)] = None
    return transtab,

现在您可以这样做了:

transtab = maketrans(inchars, outchars, deletechars)
return s.translate(*transtab).rstrip().split(':')

但实际上,我不确定这首先有什么用。你怎么能在不知道你的 incharsdeletecharsstr 还是 unicode 的情况下调用 maketranstranslate