Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 100 – Python Unicode 集成

作者:
Marc-André Lemburg <mal at lemburg.com>
状态:
最终版
类型:
标准跟踪
创建日期:
2000 年 3 月 10 日
Python 版本:
2.0
发布历史:


目录

历史注记

本文档最初由 Marc-Andre 在 PEP 之前编写,最初以 Misc/unicode.txt 形式分发在 Python 2.1 及之前的 Python 发行版中。该位置的提案的最后修订版本标记为 1.7 版(CVS 修订版 3.10)。由于该文档在 PEP 时代之后显然具有信息性 PEP 的作用,因此已将其移至此处并重新格式化以符合 PEP 指南。未来的修订将对本文档进行,而 Misc/unicode.txt 将包含指向此 PEP 的指针。

-Barry Warsaw,PEP 编辑器

引言

本提案的目的是以尽可能简单的方式向 Python 添加原生 Unicode 3.0 支持,而不会引入太多陷阱。

由于这个目标并非易事——字符串是 Python 中最基本的对象之一——我们预计本提案将经历一些重大修改。

请注意,由于 Unicode-Python 集成的许多不同方面,本提案的当前版本仍有些混乱。

本文档的最新版本始终可在以下网址获取:http://starship.python.net/~lemburg/unicode-proposal.txt

旧版本可在以下网址获取:http://starship.python.net/~lemburg/unicode-proposal-X.X.txt

[编者注:新的修订应在此 PEP 文档中进行,而版本 1.7 之前的历史记录应从 MAL 的 URL 或 Misc/unicode.txt 中检索]

约定

  • 在示例中,我们使用 u = Unicode 对象和 s = Python 字符串
  • “XXX”标记表示讨论点 (POD)

一般说明

  • Unicode 编码名称在输出时应为小写,在输入时应不区分大小写(所有接受编码名称作为输入的 API 都会将其转换为小写)。
  • 编码名称应遵循 Unicode 联盟使用的命名约定:空格转换为连字符,例如,“utf 16”写为“utf-16”。
  • 编解码器模块应使用相同的名称,但连字符转换为下划线,例如 utf_8utf_16iso_8859_1

Unicode 默认编码

Unicode 实现必须对传递给它用于强制转换的 8 位字符串的编码做出一些假设,并对在未给出特定编码时将 Unicode 转换为字符串的默认编码做出假设。此编码在本文中称为 <默认编码>。

为此,实现维护一个全局变量,可以在 site.py Python 启动脚本中设置。后续更改是不可能的。<默认编码> 可以使用两个 sys 模块 API 进行设置和查询

sys.setdefaultencoding(encoding)
设置 Unicode 实现使用的 <默认编码>。编码必须是 Python 安装支持的编码,否则会引发 LookupError。

注意:此 API 仅在 site.py 中可用!使用后,site.py 会将其从 sys 模块中移除。

sys.getdefaultencoding()
返回当前的 <默认编码>。

如果未另行定义或设置,<默认编码> 默认为“ascii”。此编码也是 Python 的启动默认值(并在 site.py 执行之前生效)。

请注意,默认的 site.py 启动模块包含禁用的可选代码,可以根据当前区域设置定义的编码设置 <默认编码>。locale 模块用于从 OS 环境定义的区域设置默认设置中提取编码(请参阅 locale.py)。如果无法确定编码,或者编码未知或不受支持,则代码默认为将 <默认编码> 设置为“ascii”。要启用此代码,请编辑 site.py 文件或将相应的代码放入 Python 安装的 sitecustomize.py 模块中。

Unicode 构造函数

Python 应提供一个内置的 Unicode 字符串构造函数,可通过 __builtins__ 访问

u = unicode(encoded_string[,encoding=<default encoding>][,errors="strict"])

u = u'<unicode-escape encoded Python string>'

u = ur'<raw-unicode-escape encoded Python string>'

“unicode-escape”编码定义如下:

  • 所有非转义字符都表示为 Unicode 序数(例如,“a”-> U+0061)。
  • 所有现有定义的 Python 转义序列都解释为 Unicode 序数;请注意,\xXXXX 可以表示所有 Unicode 序数,\OOO(八进制)可以表示高达 U+01FF 的 Unicode 序数。
  • 一个新的转义序列 \uXXXX 表示 U+XXXX;\u 后面的数字少于 4 位是语法错误。

有关 errors 可能值 的解释,请参阅下面的编解码器部分。

示例

u'abc'          -> U+0061 U+0062 U+0063
u'\u1234'       -> U+1234
u'abc\u1234\n'  -> U+0061 U+0062 U+0063 U+1234 U+005c

“raw-unicode-escape”编码定义如下:

  • \uXXXX 序列表示 U+XXXX Unicode 字符当且仅当前导反斜杠的数量为奇数时。
  • 所有其他字符都表示为 Unicode 序数(例如,“b”-> U+0062)。

请注意,您应该在源文件的前几行注释中以 pragma 行的形式提供您用于编写程序的编码提示(例如,“# source file encoding: latin-1”)。如果您只使用 7 位 ASCII,那么一切都很好,不需要这样的通知,但如果您包含 ASCII 中未定义的 Latin-1 字符,那么提供一个提示可能非常有价值,因为其他国家/地区的人也会希望能够阅读您的源字符串。

Unicode 类型对象

Unicode 对象应具有类型 UnicodeType,类型名称为“unicode”,通过标准 types 模块提供。

Unicode 输出

Unicode 对象有一个方法 .encode([encoding=<default encoding>]),它返回一个 Python 字符串,使用给定的方案(参见 Codecs)对 Unicode 字符串进行编码。

print u := print u.encode()   # using the <default encoding>

str(u)  := u.encode()         # using the <default encoding>

repr(u) := "u%s" % repr(u.encode('unicode-escape'))

另请参阅内部参数解析和缓冲区接口,了解用 C 编写的其他 API 将如何处理 Unicode 对象的详细信息。

Unicode 序数

由于 Unicode 3.0 具有 32 位序数字符集,因此实现应提供 32 位感知的序数转换 API

ord(u[:1]) (this is the standard ord() extended to work with Unicode
            objects)
  --> Unicode ordinal number (32-bit)

unichr(i)
    --> Unicode object for character i (provided it is 32-bit);
        ValueError otherwise

这两个 API 都应该像它们的字符串对应物 ord()chr() 一样放入 __builtins__ 中。

请注意,Unicode 为私有编码提供了空间。使用这些编码可能会导致在不同机器上产生不同的输出表示。这个问题不是 Python 或 Unicode 问题,而是机器设置和维护问题。

比较和哈希值

Unicode 对象应在其他对象强制转换为 Unicode 后与这些其他对象进行相等比较。对于字符串,这意味着它们使用 <默认编码> 解释为 Unicode 字符串。

Unicode 对象应返回与其 ASCII 等效字符串相同的哈希值。包含非 ASCII 值的 Unicode 字符串不能保证返回与默认编码的等效字符串表示相同的哈希值。

当使用 cmp()(或 PyObject_Compare())进行比较时,实现应屏蔽在转换过程中引发的 TypeErrors,以保持与字符串行为的同步。在将字符串强制转换为 Unicode 过程中引发的所有其他错误(例如 ValueErrors)不应被屏蔽,而应传递给用户。

在包含测试中('a' in u'abc' 和 u'a' in 'abc'),在应用测试之前,两边都应强制转换为 Unicode。在强制转换过程中发生的错误(例如 None in u'abc')不应被屏蔽。

强制转换

使用 Python 字符串和 Unicode 对象创建新对象时,应始终强制转换为更精确的格式,即 Unicode 对象。

u + s := u + unicode(s)

s + u := unicode(s) + u

所有字符串方法都应将调用委托给等效的 Unicode 对象方法调用,方法是将所有涉及的字符串转换为 Unicode,然后将参数应用于同名的 Unicode 方法,例如

string.join((s,u),sep) := (s + sep) + u

sep.join((s,u)) := (s + sep) + u

有关 Unicode 对象的 %-格式化的讨论,请参阅格式化标记。

异常

UnicodeError 在 exceptions 模块中定义为 ValueError 的子类。它在 C 级别可通过 PyExc_UnicodeError 获得。所有与 Unicode 编码/解码相关的异常都应是 UnicodeError 的子类。

编解码器(编码器/解码器)查找

编解码器(参见编解码器接口定义)搜索注册表应由模块“codecs”实现

codecs.register(search_function)

搜索函数应接受一个参数,即全部小写字母且连字符和空格转换为下划线的编码名称,并返回一个函数元组(encoder、decoder、stream_reader、stream_writer),这些函数接受以下参数:

编码器和解码器
这些必须是函数或方法,其接口与 Codec 实例的 .encode/.decode 方法相同(参见 Codec 接口)。函数/方法应以无状态模式工作。
流读取器和流写入器
这些需要是具有以下接口的工厂函数
factory(stream,errors='strict')

工厂函数必须返回提供由 StreamWriter/StreamReader 分别定义的接口的对象(参见编解码器接口)。流编解码器可以维护状态。

errors 的可能值在下面的编解码器部分中定义。

如果搜索函数找不到给定的编码,它应该返回 None。

编码的别名支持由搜索函数实现。

codecs 模块将出于性能原因维护一个编码缓存。编码首先在缓存中查找。如果未找到,则扫描已注册的搜索函数列表。如果未找到编解码器元组,则引发 LookupError。否则,编解码器元组将存储在缓存中并返回给调用者。

要查询 Codec 实例,应使用以下 API

codecs.lookup(encoding)

这将返回找到的编解码器元组或引发 LookupError

标准编解码器

标准编解码器应位于标准 Python 代码库中的 encodings/ 包目录内。该目录的 __init__.py 文件应包含一个与 Codec Lookup 兼容的搜索函数,该函数实现基于惰性模块的编解码器查找。

Python 应该为最相关的编码提供一些标准编解码器,例如

'utf-8':              8-bit variable length encoding
'utf-16':             16-bit variable length encoding (little/big endian)
'utf-16-le':          utf-16 but explicitly little endian
'utf-16-be':          utf-16 but explicitly big endian
'ascii':              7-bit ASCII codepage
'iso-8859-1':         ISO 8859-1 (Latin 1) codepage
'unicode-escape':     See Unicode Constructors for a definition
'raw-unicode-escape': See Unicode Constructors for a definition
'native':             Dump of the Internal Format used by Python

默认情况下也应提供常见别名,例如“latin-1”代表“iso-8859-1”。

注意:“utf-16”的实现应通过使用并要求字节顺序标记 (BOM) 进行文件输入/输出。

所有其他编码,例如支持亚洲脚本的 CJK 编码,应在单独的包中实现,这些包不包含在核心 Python 发行版中,也不属于本提案的一部分。

编解码器接口定义

以下基类应在“codecs”模块中定义。它们不仅为编码模块实现者提供模板,还定义了 Unicode 实现所需的接口。

请注意,此处定义的编解码器接口非常适合更广泛的应用程序。Unicode 实现期望 .encode() 的输入是 Unicode 对象,.write() 的输入是字符缓冲区兼容对象,.decode() 的输入是字符缓冲区兼容对象。 .encode().read() 的输出应为 Python 字符串,.decode() 必须返回 Unicode 对象。

首先,我们有无状态的编码器/解码器。它们不像流编解码器(见下文)那样分块工作,因为所有组件都应在内存中可用。

class Codec:

    """Defines the interface for stateless encoders/decoders.

       The .encode()/.decode() methods may implement different
       error handling schemes by providing the errors argument.
       These string values are defined:

         'strict'  - raise an error (or a subclass)
         'ignore'  - ignore the character and continue with the next
         'replace' - replace with a suitable replacement character;
                     Python will use the official U+FFFD
                     REPLACEMENT CHARACTER for the builtin Unicode
                     codecs.
    """

    def encode(self,input,errors='strict'):

        """Encodes the object input and returns a tuple (output
           object, length consumed).

           errors defines the error handling to apply.  It
           defaults to 'strict' handling.

           The method may not store state in the Codec instance.
           Use StreamCodec for codecs which have to keep state in
           order to make encoding/decoding efficient.
        """

    def decode(self,input,errors='strict'):

        """Decodes the object input and returns a tuple (output
           object, length consumed).

           input must be an object which provides the
           bf_getreadbuf buffer slot.  Python strings, buffer
           objects and memory mapped files are examples of objects
           providing this slot.

           errors defines the error handling to apply.  It
           defaults to 'strict' handling.

           The method may not store state in the Codec instance.
           Use StreamCodec for codecs which have to keep state in
           order to make encoding/decoding efficient.

        """

StreamWriterStreamReader 定义了有状态编码器/解码器的接口,它们在流上工作。这些允许数据分块处理以有效利用内存。如果内存中有大字符串,您可能希望将它们包装在 cStringIO 对象中,然后对它们使用这些编解码器,以便也能进行分块处理,例如,向用户提供进度信息。

class StreamWriter(Codec):

    def __init__(self,stream,errors='strict'):

        """Creates a StreamWriter instance.

           stream must be a file-like object open for writing
           (binary) data.

           The StreamWriter may implement different error handling
           schemes by providing the errors keyword argument.
           These parameters are defined:

             'strict' - raise a ValueError (or a subclass)
             'ignore' - ignore the character and continue with the next
             'replace'- replace with a suitable replacement character
        """
        self.stream = stream
        self.errors = errors

    def write(self,object):

        """Writes the object's contents encoded to self.stream.
        """
        data, consumed = self.encode(object,self.errors)
        self.stream.write(data)

    def writelines(self, list):

        """Writes the concatenated list of strings to the stream
           using .write().
        """
        self.write(''.join(list))

    def reset(self):

        """Flushes and resets the codec buffers used for keeping state.

           Calling this method should ensure that the data on the
           output is put into a clean state, that allows appending
           of new fresh data without having to rescan the whole
           stream to recover state.
        """
        pass

    def __getattr__(self,name, getattr=getattr):

        """Inherit all other methods from the underlying stream.
        """
        return getattr(self.stream,name)


class StreamReader(Codec):

    def __init__(self,stream,errors='strict'):

        """Creates a StreamReader instance.

           stream must be a file-like object open for reading
           (binary) data.

           The StreamReader may implement different error handling
           schemes by providing the errors keyword argument.
           These parameters are defined:

             'strict' - raise a ValueError (or a subclass)
             'ignore' - ignore the character and continue with the next
             'replace'- replace with a suitable replacement character;
        """
        self.stream = stream
        self.errors = errors

    def read(self,size=-1):

        """Decodes data from the stream self.stream and returns the
           resulting object.

           size indicates the approximate maximum number of bytes
           to read from the stream for decoding purposes.  The
           decoder can modify this setting as appropriate.  The
           default value -1 indicates to read and decode as much
           as possible.  size is intended to prevent having to
           decode huge files in one step.

           The method should use a greedy read strategy meaning
           that it should read as much data as is allowed within
           the definition of the encoding and the given size, e.g.
           if optional encoding endings or state markers are
           available on the stream, these should be read too.
        """
        # Unsliced reading:
        if size < 0:
            return self.decode(self.stream.read())[0]

        # Sliced reading:
        read = self.stream.read
        decode = self.decode
        data = read(size)
        i = 0
        while 1:
            try:
                object, decodedbytes = decode(data)
            except ValueError,why:
                # This method is slow but should work under pretty
                # much all conditions; at most 10 tries are made
                i = i + 1
                newdata = read(1)
                if not newdata or i > 10:
                    raise
                data = data + newdata
            else:
                return object

    def readline(self, size=None):

        """Read one line from the input stream and return the
           decoded data.

           Note: Unlike the .readlines() method, this method
           inherits the line breaking knowledge from the
           underlying stream's .readline() method -- there is
           currently no support for line breaking using the codec
           decoder due to lack of line buffering.  Subclasses
           should however, if possible, try to implement this
           method using their own knowledge of line breaking.

           size, if given, is passed as size argument to the
           stream's .readline() method.
        """
        if size is None:
            line = self.stream.readline()
        else:
            line = self.stream.readline(size)
        return self.decode(line)[0]

    def readlines(self, sizehint=0):

        """Read all lines available on the input stream
           and return them as list of lines.

           Line breaks are implemented using the codec's decoder
           method and are included in the list entries.

           sizehint, if given, is passed as size argument to the
           stream's .read() method.
        """
        if sizehint is None:
            data = self.stream.read()
        else:
            data = self.stream.read(sizehint)
        return self.decode(data)[0].splitlines(1)

    def reset(self):

        """Resets the codec buffers used for keeping state.

           Note that no stream repositioning should take place.
           This method is primarily intended to be able to recover
           from decoding errors.

        """
        pass

    def __getattr__(self,name, getattr=getattr):

        """ Inherit all other methods from the underlying stream.
        """
        return getattr(self.stream,name)

流编解码器实现者可以自由地将 StreamWriterStreamReader 接口组合成一个类。甚至可以将所有这些与 Codec 类结合起来。

实现者可以自由添加额外的方法来增强编解码器功能或提供它们工作所需的额外状态信息。不过,内部编解码器实现将只使用上述接口。

Unicode 实现不要求使用这些基类,只要求接口匹配;这允许将 Codec 编写为扩展类型。

作为指导,大型映射表应使用单独(共享)扩展模块中的静态 C 数据实现。这样,多个进程可以共享相同的数据。

应提供一个自动将 Unicode 映射文件转换为映射模块的工具,以简化对额外映射的支持(请参阅参考文献)。

空白

.split() 方法需要了解 Unicode 中什么是空白符。

大小写转换

大小写转换对于 Unicode 数据来说相当复杂,因为需要考虑许多不同的条件。请参阅

以获取有关实现大小写转换的一些指南。

对于 Python,我们只应实现 Unicode 中包含的 1-1 转换。与区域设置相关和其他特殊情况的转换(请参阅 Unicode 标准文件 SpecialCasing.txt)应留给用户例程,而不应进入核心解释器。

方法 .capitalize().iscapitalized() 应尽可能严格遵循上述技术报告中定义的大小写映射算法。

换行符

换行应针对所有具有 B 属性的 Unicode 字符以及组合 CRLF、CR、LF(按此顺序解释)和标准定义的其他特殊行分隔符进行。

Unicode 类型应提供一个 .splitlines() 方法,该方法根据上述规范返回行列表。参见 Unicode 方法。

Unicode 字符属性

一个单独的模块“unicodedata”应提供一个紧凑的接口,用于访问标准 UnicodeData.txt 文件中定义的所有 Unicode 字符属性。

除此之外,这些属性还提供了识别数字、位数、空格、空白等的方法。

由于此模块必须提供对所有 Unicode 字符的访问,因此它最终将不得不包含 UnicodeData.txt 中的数据,该数据大约占用 600kB。因此,数据应存储在静态 C 数据中。这使得可以将其编译为共享模块,底层操作系统可以在进程之间共享(与普通的 Python 代码模块不同)。

应该有一个标准的 Python 接口来访问此信息,以便其他实现者可以插入他们自己可能增强的版本,例如,那些即时解压数据的版本。

私有代码点区域

对这些的支持留给用户层 Codecs,未明确集成到核心中。请注意,由于内部格式的实现,只有 \uE000\uF8FF 之间的区域可用于私有编码。

内部格式

Unicode 对象的内部格式应使用 Python 特定的固定格式 <PythonUnicode>,实现为“无符号短整型”(或另一个 16 位无符号数字类型)。字节序取决于平台。

此格式将保存相应 Unicode 序数的 UTF-16 编码。Python Unicode 实现将这些值视为 UCS-2 值。对于所有当前定义的 Unicode 字符点,UCS-2 和 UTF-16 是相同的。不带代理项的 UTF-16 提供对大约 64k 个字符的访问,并覆盖 Unicode 基本多语言平面 (BMP) 中的所有字符。

编解码器有责任确保它们传递给 Unicode 对象构造函数的数据符合此假设。构造函数不检查数据是否符合 Unicode 标准或使用代理项。

未来的实现可以将 32 位限制扩展到所有 UTF-16 可寻址字符的完整集合(大约 1M 个字符)。

Unicode API 应提供从 <PythonUnicode> 到编译器的 wchar_t 的接口例程,wchar_t 可以是 16 位或 32 位,具体取决于所使用的编译器/libc/平台。

Unicode 对象应包含一个指向缓存的 Python 字符串对象 <defenc> 的指针,该对象使用 <默认编码> 保存对象的值。出于性能和内部解析(参见内部参数解析)的原因,这是必需的。当对对象发出第一次转换为 <默认编码> 的请求时,缓冲区将被填充。

目前不需要 interned,因为 Python 标识符被定义为仅 ASCII。

codecs.BOM 应该返回内部使用的格式的字节顺序标记 (BOM)。codecs 模块应该提供以下额外的常量以方便和参考(codecs.BOM 将根据平台是 BOM_BE 还是 BOM_LE

BOM_BE: '\376\377'
  (corresponds to Unicode U+0000FEFF in UTF-16 on big endian
   platforms == ZERO WIDTH NO-BREAK SPACE)

BOM_LE: '\377\376'
  (corresponds to Unicode U+0000FFFE in UTF-16 on little endian
   platforms == defined as being an illegal Unicode character)

BOM4_BE: '\000\000\376\377'
  (corresponds to Unicode U+0000FEFF in UCS-4)

BOM4_LE: '\377\376\000\000'
  (corresponds to Unicode U+0000FFFE in UCS-4)

请注意,Unicode 认为大端字节序是“正确”的。交换顺序被认为是“错误”格式的指示符,因此是非法字符定义。

配置脚本应帮助决定 Python 是否可以使用本地 wchar_t 类型(它必须是 16 位无符号类型)。

缓冲区接口

使用 <defenc> Python 字符串对象作为 bf_getcharbuf 的基础,并使用内部缓冲区作为 bf_getreadbuf 的基础实现缓冲区接口。如果请求 bf_getcharbuf 且 <defenc> 对象尚不存在,则首先创建它。

请注意,作为特例,解析器标记“s#”不会返回原始 Unicode UTF-16 数据(bf_getreadbuf 返回的数据),而是尝试使用默认编码对 Unicode 对象进行编码,然后返回指向结果字符串对象的指针(如果转换失败,则引发异常)。这样做是为了防止意外地将二进制数据写入输出流,而接收端可能无法识别。

这样做的好处是可以在不额外指定要使用的编码的情况下写入输出流(通常使用此接口)。

如果您需要访问 Unicode 对象的读取缓冲区接口,请使用 PyObject_AsReadBuffer() 接口。

内部格式也可以使用“unicode-internal”编解码器访问,例如通过 u.encode('unicode-internal')

Pickle/封送处理

应该有原生的 Unicode 对象支持。对象应该使用平台无关的编码进行编码。

Marshal 应使用 UTF-8,而 Pickle 应选择 Raw-Unicode-Escape(文本模式)或 UTF-8(二进制模式)作为编码。使用 UTF-8 而不是 UTF-16 的优点是无需存储 BOM 标记。

正则表达式

Secret Labs AB 正在开发一种支持 Unicode 的正则表达式引擎。它适用于普通的 8 位、UCS-2 和(可选)UCS-4 内部字符缓冲区。

另见

关于如何处理 Unicode RE 的一些说明。

格式化标记

格式标记在 Python 格式字符串中使用。如果 Python 字符串用作格式字符串,则以下解释应生效

'%s': For Unicode objects this will cause coercion of the
      whole format string to Unicode.  Note that you should use
      a Unicode format string to start with for performance
      reasons.

如果格式字符串是 Unicode 对象,则所有参数首先被强制转换为 Unicode,然后根据格式字符串组合和格式化。数字首先转换为字符串,然后转换为 Unicode。

'%s': Python strings are interpreted as Unicode
      string using the <default encoding>.  Unicode objects are
      taken as is.

所有其他字符串格式化程序应相应地工作。

示例

u"%s %s" % (u"abc", "abc")  ==  u"abc abc"

内部参数解析

这些标记由 PyArg_ParseTuple() API 使用。

“U”
检查 Unicode 对象并返回指向它的指针。
“s”
对于 Unicode 对象:返回指向对象的 <defenc> 缓冲区(使用 <默认编码>)的指针。
“s#”
访问 Unicode 对象的默认编码版本(参见缓冲区接口);请注意,长度与默认编码字符串的长度相关,而不是 Unicode 对象的长度。
“t#”
与“s#”相同。
“es”
接受两个参数:编码 (const char *) 和缓冲区 (char **)。

输入对象首先以通常方式强制转换为 Unicode,然后使用给定的编码编码为字符串。

在输出时,分配所需大小的缓冲区并通过 *buffer 作为 NULL 终止字符串返回。编码可能不包含嵌入的 NULL 字符。调用者负责在使用后调用 PyMem_Free() 释放已分配的 *buffer

“es#”
接受三个参数:编码 (const char *)、缓冲区 (char **) 和 buffer_len (int *)。

输入对象首先以通常方式强制转换为 Unicode,然后使用给定的编码编码为字符串。

如果 *buffer 为非 NULL,则在输入时必须将 *buffer_len 设置为 sizeof(buffer)。然后将输出复制到 *buffer

如果 *buffer 为 NULL,则分配所需大小的缓冲区并将输出复制到其中。*buffer 然后更新以指向已分配的内存区域。调用者负责在使用后调用 PyMem_Free() 释放已分配的 *buffer

在这两种情况下,*buffer_len 都更新为写入的字符数(不包括尾随的 NULL 字节)。输出缓冲区保证以 NULL 终止。

示例

使用“es#”进行自动分配

static PyObject *
test_parser(PyObject *self,
            PyObject *args)
{
    PyObject *str;
    const char *encoding = "latin-1";
    char *buffer = NULL;
    int buffer_len = 0;

    if (!PyArg_ParseTuple(args, "es#:test_parser",
                          encoding, &buffer, &buffer_len))
        return NULL;
    if (!buffer) {
        PyErr_SetString(PyExc_SystemError,
                        "buffer is NULL");
        return NULL;
    }
    str = PyString_FromStringAndSize(buffer, buffer_len);
    PyMem_Free(buffer);
    return str;
}

使用“es”进行自动分配以返回 NULL 终止字符串

static PyObject *
test_parser(PyObject *self,
            PyObject *args)
{
    PyObject *str;
    const char *encoding = "latin-1";
    char *buffer = NULL;

    if (!PyArg_ParseTuple(args, "es:test_parser",
                          encoding, &buffer))
        return NULL;
    if (!buffer) {
        PyErr_SetString(PyExc_SystemError,
                        "buffer is NULL");
        return NULL;
    }
    str = PyString_FromString(buffer);
    PyMem_Free(buffer);
    return str;
}

使用预分配缓冲区的“es#”

static PyObject *
test_parser(PyObject *self,
            PyObject *args)
{
    PyObject *str;
    const char *encoding = "latin-1";
    char _buffer[10];
    char *buffer = _buffer;
    int buffer_len = sizeof(_buffer);

    if (!PyArg_ParseTuple(args, "es#:test_parser",
                          encoding, &buffer, &buffer_len))
        return NULL;
    if (!buffer) {
        PyErr_SetString(PyExc_SystemError,
                        "buffer is NULL");
        return NULL;
    }
    str = PyString_FromStringAndSize(buffer, buffer_len);
    return str;
}

文件/流输出

由于 file.write(object) 和大多数其他流写入器使用“s#”或“t#”参数解析标记来查询要写入的数据,因此 Unicode 对象的默认编码字符串版本将被写入流(参见缓冲区接口)。

对于使用 Unicode 显式处理文件,应使用通过 codecs 模块提供的标准流编解码器。

codecs 模块应提供一个便捷的 open(filename,mode,encoding) 接口,该接口还确保在需要时模式包含“b”字符。

文件/流输入

只有用户知道输入数据使用什么编码,因此不应用任何特殊魔法。用户必须根据需要明确地将字符串数据转换为 Unicode 对象,或者使用 codecs 模块中定义的文件包装器(参见文件/流输出)。

Unicode 方法和属性

所有 Python 字符串方法,以及

.encode([encoding=<default encoding>][,errors="strict"])
   --> see Unicode Output

.splitlines([include_breaks=0])
   --> breaks the Unicode string into a list of (Unicode) lines;
       returns the lines with line breaks included, if
       include_breaks is true.  See Line Breaks for a
       specification of how line breaking is done.

代码库

我们应该使用 Fredrik Lundh 的 Unicode 对象实现作为基础。它已经实现了所需的大多数字符串方法,并提供了一个编写良好的代码库,我们可以在此基础上进行构建。

Fredrik 实现中实现的对象共享应该被放弃。

测试用例

测试用例应遵循 Lib/test/test_string.py 中的那些,并包含对 Codec Registry 和 Standard Codecs 的额外检查。

参考资料

本提案的历史

[编者注:版本 1.7 之前的修订版本可在标准 Python 发行版中 Misc/unicode.txt 的 CVS 历史记录中找到。所有后续历史记录可通过此文件的 CVS 修订版本获得。]

1.7

  • 补充了关于“s#”行为改变的说明。

1.6

  • 将 <defencstr> 更改为 <defenc>,因为这是实现中使用的名称。
  • 添加了关于在缓冲区协议实现中使用 <defenc> 的说明。

1.5

  • 添加了关于设置 <默认编码> 的说明。
  • 修复了一些拼写错误(感谢 Andrew Kuchling)。
  • 将 <defencstr> 更改为 <utf8str>。

1.4

  • 添加了关于混合类型比较和包含测试的注释。
  • 更改了格式字符串中 Unicode 对象的处理方式(如果与 '%s' % u 一起使用,它们现在将导致格式字符串被强制转换为 Unicode,从而在返回时生成一个 Unicode 对象)。
  • 添加了指向 IANA 字符集名称的链接(感谢 Lars Marius Garshol)。
  • 添加了新的编解码器方法 .readline().readlines().writelines()

1.3

  • 添加了新的“es”和“es#”解析器标记。

1.2

  • 删除了关于 codecs.open() 的 POD。

1.1

  • 添加了关于比较和哈希值的注释。
  • 添加了关于大小写映射算法的注释。
  • 更改了流编解码器的 .read().write() 方法以匹配标准类文件对象方法(方法不再返回已消耗的字节信息)。

1.0

  • 将 encode Codec 方法更改为与 decode 方法对称(现在它们都返回 (对象,已消耗数据),从而可以互换);
  • 删除了 Codec 类的 __init__ 方法(这些方法是无状态的),并将 errors 参数下移到方法中;
  • 使 Codec 设计在输入和输出对象的类型方面更加通用;
  • StreamWriter.flush 更改为 StreamWriter.reset,以避免覆盖流的 .flush() 方法;
  • .breaklines() 重命名为 .splitlines()
  • 将模块 unicodec 重命名为 codecs;
  • 修改了文件 I/O 部分以引用流编解码器。

0.9

  • 更改了 errors 关键字参数定义;
  • 添加了“替换”错误处理;
  • 将编解码器 API 更改为接受类似缓冲区的输入对象;
  • 一些小错误修复;
  • 添加了空白符部分,并包含了具有空白符和换行符特性的 Unicode 字符的引用;
  • 添加了注释,指出搜索函数可以期望小写编码名称;
  • 删除了编解码器 API 中的切片和偏移量。

0.8

  • 添加了 encodings 包和原始 unicode 转义编码;
  • 将提案中的制表符更改为空格;
  • 添加了关于 Unicode 格式字符串的注释;
  • 添加了 .breaklines() 方法。

0.7

  • 添加了一整套新的编解码器 API;
  • 添加了不同的编码器查找方案;
  • 修正了一些名称。

0.6

  • 将“s#”更改为“t#”;
  • 将 <defencbuf> 更改为持有实际 Python 字符串对象的 <defencstr>;
  • 将缓冲区接口更改为将请求委托给 <defencstr> 的缓冲区接口;
  • 移除了对 unicodec.codecs 字典的显式引用(模块可以以适合目的的方式实现此功能);
  • 移除了可设置的默认编码;
  • UnicodeError 从 unicodec 移至 exceptions;
  • “s#”现在返回内部数据;
  • 将 UCS-2/UTF-16 检查从 Unicode 构造函数传递给编解码器。

0.5

  • sys.bom 移至 unicodec.BOM
  • 添加了关于大小写映射的部分,
  • 私有编码和 Unicode 字符属性。

0.4

  • 添加了编解码器接口,关于 % 格式化的注释,
  • 更改了一些编码细节,
  • 添加了关于流包装器的注释,
  • 修复了一些讨论点(最重要的是:内部格式),
  • 澄清了“unicode-escape”编码,添加了编码参考。

0.3

  • 增加了参考文献,对编解码器模块、内部格式、bf_getcharbuffer 和 RE 引擎的评论;
  • 增加了 Tim Peters 提出的“unicode-escape”编码,并相应地修复了 repr(u);

0.2

  • 整合了 Guido 的建议,增加了流编解码器和文件包装;

0.1

  • 第一版。

来源:https://github.com/python/peps/blob/main/peps/pep-0100.rst

最后修改:2025-02-01 08:55:40 GMT