在 Python 中使用非 ASCII 时要特别小心
Python 如何处理或无法处理模式和字符串中的非 ASCII 存在一些非常微妙的问题。更糟糕的是,这些差异会因您使用的 Python 版本而有很大差异,还取决于您是否拥有“广泛的构建”。
一般来说,当你在处理 Unicode 的东西时,具有广泛构建的 Python 3 效果最好,而具有窄构建的 Python 2 效果最差,但所有组合仍然相距甚远Perl 正则表达式是如何工作的相对于 Unicode。如果您正在寻找 Python 中的 ᴘᴄʀᴇ 模式,您可能需要寻找比其旧的 re 模块更远的地方。
如果您使用足够先进的 Python 版本,那么令人烦恼的“广泛构建”问题终于得到了彻底修复。这是the v3.3 release notes的摘录:
功能
PEP 393 引入的更改如下:
- Python 现在始终支持全部范围的 Unicode 代码点,包括非 BMP 代码点(即从 U+0000 到 U+10FFFF)。窄构建和宽构建之间的区别不再存在,Python 现在的行为类似于宽构建,即使在 Windows 下也是如此。
- 随着窄构建的消亡,窄构建特有的问题也得到了修复,例如:
-
len() 现在对于非 BMP 字符总是返回 1,所以 len('\U0010FFFF') == 1;
- 代理对不会在字符串文字中重新组合,所以
'\uDBFF\uDFFF' != '\U0010FFFF';
- 索引或切片非 BMP 字符返回预期值,因此
'\U0010FFFF'[0] 现在返回 '\U0010FFFF' 而不是 '\uDBFF';
- 标准库中的所有其他函数现在可以正确处理非 BMP 代码点。
-
sys.maxunicode 的值现在始终为 1114111(十六进制的 0x10FFFF)。 PyUnicode_GetMax() 函数仍返回 0xFFFF 或 0x10FFFF 以实现向后兼容性,并且它不应与新的 Unicode API 一起使用(请参阅 issue 13054)。
-
The ./configure 标志 --with-wide-unicode 已被删除。
Python 正则表达式的未来
与标准 Python 发行版的 re 库中当前可用的内容相比,Matthew Barnett’s regex module for both Python 2 and Python 3 alike 在几乎所有可能的方面都要好得多,并且很可能最终会取代 re。它与您的问题特别相关的是,他的 regex 库在各个方面都比 re 现在更 ᴘᴄʀᴇ(ie 它与 Perl 更兼容),这将使您更容易将 Perl 正则表达式移植到 Python。因为它是一个彻底的重写(就像从头开始,而不是像汉堡包:),所以它是在考虑非 ASCII 的情况下编写的,re 不是。
因此,regex 库在处理问题方面更接近于 UTS#18: Unicode Regular Expressions 的(当前)建议。它满足或超过了 UTS#18 1 级要求,在大多数方面(如果不是全部),您通常必须使用 ICU 正则表达式库或 Perl 本身——或者如果您特别勇敢,新的 Java 7 更新其正则表达式,因为这也符合来自 UTS#18 的Level One requirements。
除了满足那些对于基本 Unicode 支持绝对必要的要求之外,Python 的当前re 库不满足这些要求,很棒的regex 库也满足了级别RL2.5 Named Characters (\N{...}))、RL2.2 Extended Grapheme Clusters (\X) 和来自 revision 14 of UTS#18 的完整属性的新 RL2.7 的两个要求。
Matthew 的 regex 模块还可以进行 Unicode 大小写折叠,以便不区分大小写的匹配在 Unicode 上可靠地工作,re 没有。
以下内容不再正确,因为regex 现在支持完整的 Unicode 大小写折叠,如 Perl 和 Ruby。
一个非常小的区别是,目前,Perl 的不区分大小写模式使用完全面向字符串的大小写折叠,而他的regex 模块仍然使用简单的面向单字符的大小写折叠,但这是他正在研究的东西.这实际上是一个非常难的问题,除了 Perl 之外,甚至只有 Ruby 尝试过。
在完全大小写的情况下,这意味着(例如)"ß" 现在可以正确匹配 "SS"、"ss"、"ſſ"、"ſs"(等等),此时选择了不区分大小写的匹配。 (这在希腊文中无疑比拉丁文更重要。)
另请参阅来自 my third OSCON2011 talk 的幻灯片或文档源代码,标题为 “Unicode 支持大战:好的、坏的和(大部分)丑陋的” JavaScript、PHP、Go、Ruby、Python、Java 和 Perl 的 Unicode 支持问题。如果不能使用 Perl 正则表达式或 ICU 正则表达式库(唉,它没有命名捕获!),那么 Matthew 为 Python 编写的 regex 可能是你最好的选择。
Nᴏᴛᴀ Bᴇɴᴇ s.ᴠ.ᴘ。 (= s'il vous plaît, et meme s'il ne vous plaît pas :) 以下未经请求的非商业非广告不是 实际上是由 Python regex 库的作者放在这里的。 :)
酷regex功能
Python regex 库具有丰富的功能,其中一些在任何其他正则表达式系统中都找不到。无论您碰巧使用它是因为它的ᴘᴄʀᴇ-ness 还是其出色的 Unicode 支持,这些都非常值得一试。
本模块的一些突出特点是:
-
Variable-width lookbehind,这是一个在正则表达式引擎中非常罕见的功能,当你真正想要它时却没有它非常令人沮丧。这很可能是正则表达式中最常被请求的功能。
- 向后搜索,因此您不必先自己反转字符串。
- 作用域
ismx-type 选项,因此(?i:foo) 只为 foo 折叠,而不是整体,或 (?-i:foo) 仅在 foo 上关闭它。这就是 Perl 的工作方式(或可以)。
- 基于编辑距离的模糊匹配(Udi Manber 的
agrep 和 glimpse 也有)
- 通过
\L<list> 插值的隐式最短到最长排序命名列表
- 仅与单词开头或结尾而不是任一侧的特定匹配的元字符(
\m、\M)
- 支持所有 Unicode 行分隔符(Java 可以做到这一点,Perl 也可以做到这一点,尽管对于
\R per RL1.6 有点不情愿。
- 根据RL1.3 对括号字符类进行完整的操作——联合、交集、差异和对称差异,这比在 Perl 中进行操作要容易得多。
- 允许重复捕获组,例如
(\w+\s+)+,您可以在其中获取第一组的所有单独匹配项,而不仅仅是最后一个匹配项。 (我相信 C# 也可能做到这一点。)
- 一种比前瞻中偷偷摸摸的捕获组更直接的方法来获得重叠匹配。
- 所有组的开始和结束位置,用于以后的切片/子字符串操作,很像 Perl 的
@+ 和 @- 数组。
- 分支重置运算符通过
(?|...|...|...|) 重置每个分支中的组编号,就像它在 Perl 中的工作方式一样。
- 可以配置为让您的咖啡在早上等着您。
- 支持来自RL2.3 的更复杂的字边界。
- 默认假定 Unicode 字符串,并且完全支持 RL1.2a 以便
\w、\b、\s 等在 Unicode 上工作。
- 支持
\X 用于字素。
- 支持
\G 连续点断言。
- 适用于 64 位构建(
re 仅具有 32 位索引)。
- 支持多线程。
好的,炒作就够了。 :)
又一个很好的替代正则表达式引擎
如果您是正则表达式极客,那么值得一看的最后一个替代方案是 Python library bindings,而不是 Russ Cox 的出色 RE2 library。它还原生支持 Unicode,包括简单的基于字符的大小写折叠,与 re 不同的是,它特别提供了 Unicode 通用类别和 Unicode 脚本字符属性,这是您最常需要的更简单类型 Unicode 的两个关键属性加工。
虽然 RE2 错过了一些 Unicode 功能,例如 ICU、Perl 和 Python 中的 \N{...} 命名字符支持,但它具有极其强大的计算优势,使其成为首选的正则表达式引擎您担心通过 Web 查询等中的正则表达式进行的基于饥饿的拒绝服务攻击。它通过禁止反向引用来管理这一点,这会导致正则表达式不再有规律,并有可能在时间和空间上出现超指数爆炸。
RE2 的库绑定不仅适用于 C/C++ 和 Python,还适用于 Perl,尤其适用于 Go,它很快就会取代那里的标准正则表达式库。