在 python 2 和 3 中确保 unicode 的 Pythonic 方法

Pythonic way to ensure unicode in python 2 and 3

我正在努力移植一个库,以便它与 python 2 和 3 都兼容。该库从调用应用程序接收字符串或类似字符串的对象,我需要确保这些对象得到转换为 unicode 字符串。

在python2我能做到:

unicode_x = unicode(x)

在python3我能做到:

unicode_x = str(x)

不过,我目前最好的跨版本方案是:

def ensure_unicode(x):
  if sys.version_info < (3, 0):
    return unicode(x)
  return str(x)

这看起来肯定不太好(尽管它有效)。有更好的解决方案吗?

我知道 unicode_literalsu 前缀,但是这两种解决方案都不起作用,因为输入来自客户端并且不是我库中的文字。

不要重新发明兼容层轮子。使用 six compatibility layer,一个可以包含在您自己的文件中的小型单文件项目:

Six supports every Python version since 2.6. It is contained in only one Python file, so it can be easily copied into your project. (The copyright and license notice must be retained.)

它包含一个 six.text_type() callable 执行此操作,将值转换为 Unicode 文本:

import six

unicode_x = six.text_type(x)

project source code中定义为:

import sys

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
# ...

if PY3:
    # ...
    text_type = str
    # ...

else:
    # ...
    text_type = unicode
    # ...

几乎总是使用 six.text_type 就足够了,就像公认的答案所说的那样。

旁注,仅供参考,如果你以某种方式向它提供 bytes 实例,你 可能 会在 Python 3 中惹上麻烦, (虽然这应该很难做到)。

上下文

six.text_type 基本上是 Python 中 str 的别名 3:

>>> import six
>>> six.text_type
<class 'str'>

令人惊讶的是,使用 str 来转换 bytes 个实例会产生一些意想不到的结果:

>>> six.text_type(b'bytestring')
"b'bytestring'"

注意到我们的字符串是如何损坏的了吗?直接来自 strdocs:

Passing a bytes object to str() without the encoding or errors arguments falls under the first case of returning the informal string representation.

也就是说,str(...)实际上会调用对象的__str__方法,除非你传递一个encoding:

>>> b'bytestring'.__str__()
"b'bytestring'"
>>> six.text_type(b'bytestring', encoding='utf-8')
'bytestring'

遗憾的是,如果您传递 encoding,"casting" 常规 str 实例将不再有效:

>>> six.text_type('string', encoding='utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: decoding str is not supported

在某种程度上相关的注释中,转换 None 值也可能很麻烦:

>>> six.text_type(None)
'None'

你最终会得到一个 'None' 字符串。可能不是你想要的。

备选方案

  1. 就用six.text_type.真的。除非您故意与 bytes 互动,否则无需担心。不过,请务必在投射前检查 Nones。

  2. 使用 Django's force_text. 如果你碰巧在一个已经在使用 Django 的项目上工作,这是摆脱这种疯狂的最安全的方法 1.x.x.

  3. 将 Django 的 force_text 复制粘贴到您的项目中。 这是一个 sample implementation

对于任一 Django 替代方案,请记住 force_text 允许您指定 strings_only=True 以整齐地保留 None 值:

>>> force_text(None)
'None'
>>> type(force_text(None))
<class 'str'>

>>> force_text(None, strings_only=True)
>>> type(force_text(None, strings_only=True))
<class 'NoneType'>

不过要小心,因为它不会同时转换其他几个原始类型:

>>> force_text(100)
'100'
>>> force_text(100, strings_only=True)
100
>>> force_text(True)
'True'
>>> force_text(True, strings_only=True)
True

如果 Python 3 中的 six.text_type(b'foo') -> "b'foo'" 不是您在 Alex 的回答中提到的,可能您想要 six.ensure_text(),在 6 v1.12.0+ 中可用。

In [17]: six.ensure_text(b'foo')
Out[17]: 'foo'

参考:https://six.readthedocs.io/#six.ensure_text