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

时间:2015-05-10 18:13:12

标签: python unicode

这是我用来抽象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和普通字符串,而不需要关心这种类型。

我不喜欢的是:

  • 此实现依赖于检查字符串的类型以调用正确的函数
  • 我做不到txt.translate(...)(我必须做translate(txt, ...),这意味着我无法链接函数调用,如txt[:50].translate(...)

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

1 个答案:

答案 0 :(得分:1)

  

此实现依赖于检查字符串的类型以调用正确的函数

那么可以呢?你想为不同的类型做不同的事情,你不能用点式语法OO风格对它们进行monkeypatch这样的类型,那么如何自动调度类型呢?您正在寻找的是外部调度。 Python可以在3.4+中执行此操作(仅调度第一个参数,而不是所有参数,如CLOS或Dylan ......虽然PyPI上有多个调度库)singledispatch,并且PyPI上有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只删除了不必要的别名,而不是为StringType添加UnicodeTypestr两个别名并添加{{1} } ...)

如果你不想在PyPI上使用库或者自己实现相同的东西,而是想要手动切换类型,你可能需要BytesType而不是isinstance

  

我不能做txt.translate(...)(我必须做翻译(txt,...)

这是真的;你不能monkeypatch type(x) ==str。那么呢?

  

这意味着我无法链接函数调用,如txt [:50] .translate(...)

当然,但你可以链接像unicode这样的函数调用。虽然在Java或Ruby这样的“一切都是方法”的语言中看起来可能是反惯用语,但它在Python中完全没问题。特别是因为无论如何,用Python连接超过2或3个调用是非常罕见的。毕竟,translate(txt[:50], …).rstrip().split(':')之后的下一件事将是split调用或理解,而这些不是由Python中的方法完成的。

  

这里我失去了map的一些力量(即将一个字符翻译成字符串)

是的,这在最低共同点设计中几乎是固有的。一些性能损失也是如此。 unicode.translatestr.translate并没有真正做同样的事情。前者是基于表格的翻译,因为当你只有256个可能的值时,这是一个很好的优化,但它确实意味着你放弃了一些灵活性和力量。后者是基于字典的翻译,因为表格对于110万个值来说是悲观的,但这意味着你获得了一些额外的灵活性和力量。

所以,在这里,你放弃了unicode.translate的表现(特别是因为你必须为每次翻译动态构建str.translate),以及transtab的灵活性,以获得两个世界中最糟糕的。

如果您确实知道unicode.translate字符串的编码(并且它们实际上确实代表了文本 - str对二进制数据也很有用...),您可以通过以下方式编写此代码str.translate。但是,如果您知道编码,那么您最初可能只需s.decode(encoding).translate(…).encode(encoding)而不是unicode

但我认为更好的解决方案可能是以str的方式返回一个由maketrans组成的两个表的元组,以及str的一个元组的元组。然后,您可以调用原生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(':') maketranstranslate还是inchars,您怎么能打电话给deletecharsstr