Python中的原始字符串和正则表达式

8

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

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
  • 在第二个print语句的第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)

并获得以下输出:

您能具体解释这四种情况吗?
4个回答

18

你对字符串和字符串字面值的区别产生了困惑。

字符串字面值是指你放在 " 或者 ' 之间的内容,Python 解释器会解析这个字符串并将其存储到内存中。如果你将字符串字面值标记为原始字符串字面值(使用 r'),那么 Python 解释器在将其存储到内存之前不会更改该字符串的表示方式,但是一旦它们被解析,它们在内存中存储的方式完全相同。

这意味着,在内存中不存在所谓的原始字符串。以下两个字符串在内存中以相同的方式存储,没有任何关于它们是否为原始字符串的概念。

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

这两个字符串都包含\d,没有任何迹象表明这是从原始字符串中提取的内容。所以当你将此字符串传递给re模块时,它会看到有一个\d,并将其视为数字,因为re模块不知道这个字符串来自原始字符串。

在您的特定示例中,要获取一个字面上的反斜杠后跟一个字面上的小写字母"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'。有时字符串会让人感到困惑。

编辑2: 为了回答你的编辑,让我们具体看每一行:

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字符。虽然这很复杂,但事实上简单来说就是:

\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+)',正如第二个示例所解释的那样。


你能否解释一下问题中的附加部分? - fluency03
我已经尝试解释它们了。这是相当复杂的行为,所以希望我没有让你更加困惑。 - Sean1708
repl 可以是字符串或函数;如果它是一个字符串,其中的任何反斜杠转义都会被处理。也就是说,\n 被转换为单个换行符,\r 被转换为回车符等等。https://docs.python.org/3/library/re.html#re.sub - JinSnow
你可以在这里测试你的正则表达式(包括Python和其他语言):https://regex101.com/ (请检查Python版本)。 - JinSnow

6
在python中,需要区分Python解释器和re模块之间的区别。
在Python中,如果未对字符串进行原始处理,则反斜杠后跟一个字符可能表示特殊字符。例如,\n代表换行符,\r代表回车符,\t表示制表符,\b表示不可破坏的退格。但单独使用\d在Python字符串中并没有任何特殊含义。
然而,在正则表达式中,有许多字符在Python中通常不会有特殊含义。这就是问题所在,这些字符的含义不总是相同。其中一件容易产生歧义的事情是\b,在Python中它表示的是退格,在正则表达式中则表示单词边界。这意味着如果您将未经过原始处理的\b传递给正则表达式部分,则该\b将在传递给正则表达式函数之前被替换为退格,并且在那里它不起作用。因此,您必须绝对传递带有反斜杠的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中没有任何特殊含义,所以无论表达式是否为原始字符串,在re.sub中都可以传递\d
第三和第四个发生的原因是你没有使替代表达式成为原始字符串。在Python中,\1\2\3具有特殊含义,分别表示白色(或未填充)笑脸、黑色(填充)笑脸和心形(如果字符无法显示,则会得到这些“字符框”)。因此,你替换的是特定字符而不是捕获组的字符。

enter image description here


2
我觉得上面的答案过于复杂了。如果您正在运行re.search(),则发送的字符串会通过两个层次进行解析:
1. Python会将您编写的\字符​​解释为此过滤器。 2. 然后,正则表达式将解释您编写的\字符​​通过其自己的过滤器
它们按照这个顺序进行。
“原始”字符串语法r"\nlolwtfbbq"用于绕过Python解释器,它不会影响re
>>> print "\nlolwtfbbq"

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

请注意,在第一个示例中,会打印一个换行符,但在第二个示例中打印的是实际的字符\n,因为它是原始的。
您发送到re的任何字符串都通过正则表达式解释器进行解释,因此回答您特定的问题:\d在正则表达式中表示“数字0-9”。

1
并非所有的 \ 都会引起问题。Python 解释器有一些内置函数,如 \b 等。因此,如果没有 r,Python 将把 \b 视为自己的文字,而不是用于正则表达式的单词边界。当与 r(原始字符串)模式一起使用时,\b 保持不变。这是通俗易懂的语言。不太涉及技术方面。\d 在 Python 中不是特殊的内置函数,因此即使没有 r 模式也是安全的。
在这里,您可以 查看 列表。这个列表是 Python 可以理解和解释的,例如 \b, \n 而不是 \d
在第一个 print 中,\d 的解释是由 regex 模块完成的,而不是 Python。在第二个 print 中,它由 Python 完成。由于它处于 r 模式中,它将按原样输出。

你是指“解释由正则表达式完成”还是“由Python完成”?这有什么区别呢? - fluency03
1
@fluency_03 在 Python\d 没有任何意义。它是 regex 模块知道 \d 表示 [0-9]。同样的,当 Python 发现 \b\n 时也会进行解释。如果你不想让 Python 进行解释,可以将所有内容放在 r 模式下。 - vks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接