【问题标题】:Unexpected behavior of regex word boundaries with Unicode strings正则表达式单词边界与 Unicode 字符串的意外行为
【发布时间】:2016-03-31 14:52:24
【问题描述】:

有人可以解释一下正则表达式的这种行为吗:

当我用其他一些 Unicode 字符替换 Unicode 字符串的最后两个字符时,它可以在字符串末尾使用行边界 ($) 正常工作,但如果我在方括号中指定 $,则会产生意外结果[$].

此外,单词边界\b 给出了意想不到的结果,并且令人惊讶的是\B匹配了\b 应该匹配的内容。

>>> line = u'\u0627\u062f\u0646\u06cc\u0670'

>>> re.sub(ur'\u06cc\u0670$', ur'\u0627', line) #works fine
u'\u0627\u062f\u0646\u0627' 

>>> re.sub(ur'\u06cc\u0670[$]', ur'\u0627', line) #unexpected result
u'\u0627\u062f\u0646\u06cc\u0670' 

>>> re.sub(ur'\u06cc\u0670[$]', ur'\u0627', line, re.U) #still not working
u'\u0627\u062f\u0646\u06cc\u0670'

>>> re.sub(ur'\u06cc\u0670\b', ur'\u0627', line, re.U) #unexpected 
u'\u0627\u062f\u0646\u06cc\u0670'

>>> re.sub(ur'\u06cc\u0670\B', ur'\u0627', line, re.U) #unexpected
u'\u0627\u062f\u0646\u0627'

【问题讨论】:

    标签: regex string python-2.7 unicode


    【解决方案1】:
    1. re.sub的签名是:

      sub(pattern, repl, string, count=0, flags=0)
      

      re.U 标志被传递给count,所以re.U 标志什么都不做。确保使用如下关键字参数:

      re.sub(ur'\u06cc\u0670\b', u'\u0627', line, flags=re.U)
      #                                           ^~~~~~ 
      
    2. […] 定义了一个字符类,$ 在括号内并不特殊。所以[$] 只会匹配一个文字美元符号。

    3. \b 匹配单词(“\w”)和非单词(“\W”,或字符串的开头/结尾)之间的边界,\B 匹配任何不是\b 的地方。现在,\u0670 在 Unicode 中是一个非单词:

      >>> re.findall(ur'\w', line, flags=re.U)
      [u'\u0627', u'\u062f', u'\u0646', u'\u06cc']
      >>> re.findall(ur'\W', line, flags=re.U)
      [u'\u0670']
      

      这意味着\u0670 之后的字符串结尾不是 单词边界,因为\u0670 不是单词。所以\b 无法匹配它,这意味着\B 将匹配它。

      \w 在 Unicode 中的含义是“[0-9_] 加上 Unicode 字符属性数据库中的 classified as alphanumeric”。

      U+06CC(Arabic Letter Farsi Yeh)这样的字符被归类为Letter, Other (Lo) 所以它是一个单词,但是U+0670(Arabic Letter Superscript Alef)被归类为Mark, Nonspacing (Mn) 所以它被认为是一个词。

    (您可以在https://docs.python.org/2/library/re.html 中查看 Python 正则表达式语法的详细信息)


    至于下面的评论,您可以使用negative look-ahead 代替群组:

    re.sub(ur'(?:[\u06cc\u06d2]\u0670|\u0670[\u06cc\u06d2])(?!\w)', u'\u0627', line, flags=re.U)
    

    这里,

    • [\u06cc\u06d2]\u0670|\u0670[\u06cc\u06d2] 与您的 \u06cc\u0670|\u06d2\u0670|\u0670\u06cc|\u0670\u06d2 相同,但将相似的案例分组在一起
    • (?:…) 定义了一个non-capturing group,这样就可以从交替中提取出你想要的“\b”
    • (?!\w) 表示仅当下一个字符不是单词时才匹配。

    结果是这样的:

    >>> re.sub(u'(?:[\u06cc\u06d2]\u0670|\u0670[\u06cc\u06d2])(?!\w)', u'\u0627', line, flags=re.U)
    u'\u0627\u062f\u0646\u0627'
    >>> re.sub(u'(?:[\u06cc\u06d2]\u0670|\u0670[\u06cc\u06d2])(?!\w)', u'\u0627', line + u'\u0646', flags=re.U)
    u'\u0627\u062f\u0646\u06cc\u0670\u0646'
    >>> re.sub(u'(?:[\u06cc\u06d2]\u0670|\u0670[\u06cc\u06d2])(?!\w)', u'\u0627', line + u'\u061f', flags=re.U)
    u'\u0627\u062f\u0646\u0627\u061f'
    

    【讨论】:

    • 其实我想要re.sub(u'\u06cc\u0670\b|\u06d2\u0670\b|\u0670\u06cc\b|\u0670\u06d2\b', u'\u0627', line, re.U) 之类的东西,但由于\b 在这种情况下不起作用,所以我尝试了re.sub(u'\u06cc\u0670(\s)|\u06d2\u0670(\s)|\u0670\u06cc(\s)|\u0670\u06d2(\s)', ur'\u0627\1', line, re.U) 但现在我不知道我必须用@ 替换哪个组号987654354@ 或 \2 或 ... 。你能帮我解决这个问题吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    相关资源
    最近更新 更多