【问题标题】:Raw string and regular expression in PythonPython中的原始字符串和正则表达式
【发布时间】:2015-07-21 17:51:23
【问题描述】:

我对以下代码中的原始字符串感到困惑:

import re

text2 = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
text2_re = re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
print (text2_re) #output: Today is 2012-11-27. PyCon starts 2013-3-13.

print (r'(\d+)/(\d+)/(\d+)') #output: (\d+)/(\d+)/(\d+)

据我了解原始字符串,没有 r\ 被视为转义字符;使用 r,反斜杠 \ 将被视为本身(反斜杠)。

但是,我在上面的代码中无法理解的是:

  • 在正则表达式第5行中,即使有一个r,里面的“\d”也被当作一个数字[0-9] 而不是一个反斜杠 \ 加上一个字母 d
  • 在第二个打印第 8 行中,所有字符都被视为原始字符串。

有什么区别?

附加版:

我做了以下四种变体,有或没有r

import re

text2 = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
text2_re = re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
text2_re1 = re.sub('(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
text2_re2 = re.sub(r'(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)
text2_re3 = re.sub('(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)

print (text2_re)
print (text2_re1)
print (text2_re2)
print (text2_re3)

并得到以下输出:

你能具体解释一下这四种情况吗?

【问题讨论】:

    标签: python regex escaping backslash rawstring


    【解决方案1】:

    并非所有\ 都会引起问题。 Python 解释器有一些内置函数,如 \b 等。所以现在如果 r 不存在,Python 会将 \b 视为它自己的文字,而不是 word boundary 用于正则表达式。当它与r(原始字符串)模式一起使用时,\b 将保持原样。那是通俗的语言。技术方面的内容不多。\d 不是 python 中的特殊内置函数,因此即使没有r 模式也是安全的。

    您可以在这里see 列表。这是 Python 可以理解和解释的列表,例如 \b\n 而不是 \d

    在第一个print 中,\d 解释是由正则表达式模块而不是 Python 完成的。在第二个print 中,它是由 Python 完成的。因为它处于r 模式,所以它会按原样放置。

    【讨论】:

    • 你是什么意思解释是由正则表达式完成由python?有什么区别?
    • @fluency_03 \dpython 没有任何意义。它的regex 模块知道\d[0-9]。在同一行python 知道\b,\n 所以当它找到这些时,它会解释它们。所以如果你想让 python 不解释这些,你把所有东西都放在r 模式。
    【解决方案2】:

    您必须区分 python 解释器和 re 模块。

    在 python 中,如果字符串不是原始的,则后跟一个字符的反斜杠可以表示特殊字符。例如,\n 表示换行符,\r 表示回车,\t 表示制表符,\b 表示非破坏性退格。就其本身而言,python 字符串中的\d 并不意味着任何特殊。

    然而,在正则表达式中,有一堆字符在 python 中并不总是意味着任何东西。但这就是问题所在,“并非总是如此”。可能被误解的一件事是\b,在 python 中是退格,在正则表达式中表示单词边界。这意味着如果您将未处理的\b 传递给正则表达式的正则表达式部分,则此\b 将被退格替换before 它被传递给正则表达式函数并获胜没什么意思。因此,您必须绝对传递带有反斜杠的 b,为此,您要么转义反斜杠,要么原始字符串。

    回到你关于\d的问题,\d在python中没有任何特殊含义,所以它保持不变。作为正则表达式传递的相同\d 被正则表达式引擎转换,它是python 解释器的独立实体。


    每个问题的编辑:

    import re
    
    text2 = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
    text2_re = re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
    text2_re1 = re.sub('(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
    text2_re2 = re.sub(r'(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)
    text2_re3 = re.sub('(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)
    
    print(text2_re)
    print(text2_re1)
    print(text2_re2)
    print(text2_re3)
    

    前两个应该是直截了当的。 re.sub 通过匹配数字和正斜杠并用连字符以不同的顺序替换它们来完成它的工作。由于\d在python中没有任何特殊含义,所以\d传递给re.sub不管表达式是否是原始的。

    第三个和第四个发生是因为您没有为替换表达式提取字符串。 \1\2\3在python中有特殊含义,分别代表一个白色(或未填充)笑脸、一个黑色(填充)笑脸和一颗心(如果字符不能显示,你得到这些'字符框')。因此,您不是用捕获的组替换,而是用特定字符替换字符串。

    【讨论】:

      【解决方案3】:

      您对字符串和字符串文字之间的区别感到困惑。

      字符串文字是您在"' 之间放置的内容,python 解释器解析此字符串并将其放入内存。如果您将字符串文字标记为原始字符串文字(使用r'),则python解释器在将其放入内存之前不会更改该字符串的表示,但一旦它们被解析,它们就会以完全相同的方式存储。

      这意味着在内存中没有原始字符串之类的东西。以下两个字符串在内存中存储相同,不知道它们是否是原始的。

      r'a regex digit: \d'  # a regex digit: \d
      'a regex digit: \\d'  # a regex digit: \d
      

      这两个字符串都包含\d,没有什么可以说它来自原始字符串。因此,当您将此字符串传递给re 模块时,它会看到有一个\d 并将其视为一个数字,因为re 模块不知道该字符串来自原始字符串文字 em>。

      在您的具体示例中,要获取文字反斜杠后跟文字 d,您可以使用 \\d ,如下所示:

      import re
      
      text2 = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
      text2_re = re.sub(r'(\\d+)/(\\d+)/(\\d+)', r'\3-\1-\2', text2)
      print (text2_re) #output: Today is 11/27/2012. PyCon starts 3/13/2013.
      

      或者,不使用原始字符串:

      import re
      
      text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
      text_re = re.sub('(\\d+)/(\\d+)/(\\d+)', '\\3-\\1-\\2', text2)
      print (text_re) #output: Today is 2012-11-27. PyCon starts 2013-3-13.
      
      text2 = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
      text2_re = re.sub('(\\\\d+)/(\\\\d+)/(\\\\d+)', '\\3-\\1-\\2', text2)
      print (text2_re) #output: Today is 11/27/2012. PyCon starts 3/13/2013.
      

      希望对你有所帮助。

      编辑:我不想让事情复杂化,但因为\d 不是有效的转义序列,python 不会改变它,所以'\d' == r'\d' 是真的。由于\\ 一个有效的转义序列,它被更改为\,所以你得到'\d' == '\\d' == r'\d' 的行为。字符串有时会令人困惑。

      Edit2:要回答您的编辑,让我们具体看一下每一行:

      text2_re = re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
      

      re.sub 接收两个字符串 (\d+)/(\d+)/(\d+)\3-\1-\2。希望现在的行为符合您的预期。

      text2_re1 = re.sub('(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text2)
      

      再次(因为\d 不是有效的字符串转义,它不会被更改,请参阅我的第一个编辑)re.sub 接收两个字符串(\d+)/(\d+)/(\d+)\3-\1-\2。由于\d 不会被python 解释器r'(\d+)/(\d+)/(\d+)' == '(\d+)/(\d+)/(\d+)' 更改。如果您理解我的第一次编辑,那么希望您应该理解为什么这两种情况的行为相同。

      text2_re2 = re.sub(r'(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)
      

      这种情况有点不同,因为\1\2\3 都是有效的转义序列,它们被替换为unicode character,其十进制表示由数字给出。这很复杂,但基本上可以归结为:

      \1  # stands for the ascii start-of-heading character
      \2  # stands for the ascii start-of-text character
      \3  # stands for the ascii end-of-text character
      

      这意味着re.sub 接收第一个字符串,就像它在前两个示例中所做的一样((\d+)/(\d+)/(\d+)),但第二个字符串实际上是<start-of-heading>/<start-of-text>/<end-of-text>。所以re.sub 将匹配替换为第二个字符串,但是由于这三个(\1\2\3)都不是可打印字符,python 只打印一个普通的占位符字符。

      text2_re3 = re.sub('(\d+)/(\d+)/(\d+)', '\3-\1-\2', text2)
      

      这与第三个示例类似,因为 r'(\d+)/(\d+)/(\d+)' == '(\d+)/(\d+)/(\d+)',如第二个示例中所述。

      【讨论】:

      • 您能否解释一下问题中的附加部分?
      • 我已经尝试过解释它们。这是一个相当复杂的行为,所以希望我没有让你更加困惑。
      • repl 可以是字符串或函数;如果它是一个字符串,则处理其中的任何反斜杠转义。也就是说,\n 转换为单个换行符,\r 转换为回车符,等等。 docs.python.org/3/library/re.html#re.sub
      • 你可以在这里测试你的正则表达式(在 python 和其他中)regex101.com(检查 python 风格)
      【解决方案4】:

      我觉得上面的答案过于复杂了。如果你运行re.search(),你发送的字符串会经过两层解析:

      1. Python 会解释您编写的 \ 字符 through this filter

      2. 然后,正则表达式解释你写的\字符through its own filter

      它们按此顺序发生。

      “原始”字符串语法 r"\nlolwtfbbq" 用于当您想要绕过 Python 解释器时,它不会影响 re

      >>> print "\nlolwtfbbq"
      
      lolwtfbbq
      >>> print r"\nlolwtfbbq"
      \nlolwtfbbq
      >>>
      

      请注意,第一个示例打印了换行符,但实际字符 \n 打印在第二个示例中,因为它是原始的。

      您发送给re 的任何字符串都会经过正则表达式解释器,因此要回答您的具体问题,\d 表示正则表达式中的“数字 0-9”。

      【讨论】:

        猜你喜欢
        • 2015-12-18
        • 2021-10-02
        • 2019-10-24
        • 1970-01-01
        相关资源
        最近更新 更多