【问题标题】:How to get a reliable unicode character count in Python?如何在 Python 中获得可靠的 unicode 字符数?
【发布时间】:2011-10-18 20:17:36
【问题描述】:

Google App Engine 使用 Python 2.5.2,显然启用了 UCS4。但 GAE 数据存储在内部使用 UTF-8。因此,如果您将 u'\ud834\udd0c'(长度为 2)存储到数据存储区,当您检索它时,您会得到 '\U0001d10c'(长度为 1)。我试图以一种在存储之前和之后给出相同结果的方式来计算字符串中的 unicode 字符数。因此,在我收到字符串后,我会尝试对其进行标准化(从 u'\ud834\udd0c' 到 '\U0001d10c'),然后再计算其长度并将其放入数据存储区。我知道我可以将其编码为 UTF-8,然后再次解码,但有没有更直接/有效的方法?

【问题讨论】:

  • 不要尝试存储u'\ud834\udd0c'。代理不是有效的 unicode 代码点,因此您不应依赖它们保存在字符串中,或​​长度正常工作。
  • 澄清一下:Python 中的u'blah' 在概念上表示一系列 Unicode 代码点。所以你不应该将 UTF-16 的二进制表示放入其中。
  • 如果您从问题中删除了关于编码和解码的句子,并将其添加为答案,我可能会赞成,因为我认为这是最正确的事情去做得到你想要的。

标签: python google-app-engine unicode utf-16 utf-32


【解决方案1】:

我知道我可以将其编码为 UTF-8,然后再次解码

是的,当您输入“UCS-4 字符串中的 UTF-16 代理”时,这是解决问题的常用习惯用法。但正如机械蜗牛所说,这个输入 格式不正确,你应该修复任何产生它的偏好。

有没有更直接/有效的方法?

嗯...您可以使用正则表达式手动完成,例如:

re.sub(
    u'([\uD800-\uDBFF])([\uDC00-\uDFFF])',
    lambda m: unichr((ord(m.group(1))-0xD800<<10)+ord(m.group(2))-0xDC00+0x10000),
    s
)

当然不是更直接......我也怀疑它是否真的更有效!

【讨论】:

  • 谢谢。我的输入来自包含代理代码单元的 yaml 流,并且将流修复为不包含代理似乎确实可以防止它们出现在 unicode 中。所以我会考虑修复流。
  • YAML 是什么编码?如果是 UTF-16,那些代理应该被转换为单个 Unicode 字符……但是,如果是 UTF-8,那么生产者就犯了一个错误。 (UTF-8-with-surrogates 被称为“CESU-8”,不应使用。)
  • 我很确定正则表达式不会更有效率。
  • yaml 本身是一个 utf-8 编码的 str,但源数据中的 unicode 字符在 yaml 中用转义序列表示。 (这是在python 没有 UCS4的情况下使用PyYAML。)所以yaml.dump(u'\U0001D10C')产生这个str:"\uD834\uDD0C"(len=14)。
  • 在我看来,这就像 yaml.dump 中的一个错误(可能是一个仅在 Windows 等窄 Unicode 版本上运行的错误?)。 YAML 输出也应该是"\U0001D10C"
【解决方案2】:

不幸的是,CPython 解释器在 3.3 之前的版本中的行为取决于它是使用“窄”还是“宽”Unicode 支持构建的。因此,相同的代码,例如对len 的调用,在标准解释器的不同构建中可能会产生不同的结果。有关示例,请参阅this question

“窄”和“宽”之间的区别在于,“窄”解释器在内部存储 16 位代码单元 (UCS-2),而“宽”解释器在内部存储 32 位代码单元 (UCS-4)。代码 points U+10000 及以上(在基本多语言平面之外)在“窄”解释器上有两个 len,因为需要两个 UCS-2 代码 units代表他们(使用代理),这就是len 的措施。在“宽”构建上,非 BMP 代码 point 只需要一个 UCS-4 代码 unit,因此对于这些构建,len 是此类代码点的一个.

我已经确认以下处理所有unicode 字符串,无论它们是否包含代理对,并且在 CPython 2.7 中适用于窄版和宽版。 (可以说,在宽解释器中指定像 u'\ud83d\udc4d' 这样的字符串反映了将完整代理代码 point 表示为不同于部分字符代码 unit 的肯定愿望,并且是因此不会自动纠正错误,但我在这里忽略了这一点。这是一个边缘情况,通常不是所需的用例。)

下面使用的@invoke 技巧是一种避免重复计算而不向模块的__dict__ 添加任何内容的方法。

invoke = lambda f: f()  # trick taken from AJAX frameworks

@invoke
def codepoint_count():
  testlength = len(u'\U00010000')  # pre-compute once
  assert (testlength == 1) or (testlength == 2)
  if testlength == 1:
    def closure(data):  # count function for "wide" interpreter
      u'returns the number of Unicode code points in a unicode string'
      return len(data.encode('UTF-16BE').decode('UTF-16BE'))
  else:
    def is_surrogate(c):
      ordc = ord(c)
      return (ordc >= 55296) and (ordc < 56320)
    def closure(data):  # count function for "narrow" interpreter
      u'returns the number of Unicode code points in a unicode string'
      return len(data) - len(filter(is_surrogate, data))
  return closure

assert codepoint_count(u'hello \U0001f44d') == 7
assert codepoint_count(u'hello \ud83d\udc4d') == 7

【讨论】:

    猜你喜欢
    • 2011-01-01
    • 2012-04-17
    • 2015-09-02
    • 1970-01-01
    • 2014-08-22
    • 2017-02-14
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多