geekdoc-python-zh/docs/pythoncentral/introduction-to-python-regu...

267 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python 正则表达式简介
> 原文:<https://www.pythoncentral.io/introduction-to-python-regular-expressions/>
在 Python 中,在一个字符串中搜索另一个字符串相当容易:
```py
>>> str = 'Hello, world'
>>> print(str.find('wor'))
7
```
如果我们确切地知道*我们要找的是什么,这很好,但是如果我们要找的东西不是那么明确呢?例如,如果我们想搜索一年,那么我们知道这将是一个 4 位数的序列,但我们不知道这些数字将会是什么。这就是*正则表达式*的用武之地。它们允许我们基于我们所寻找的内容的一般描述来搜索子字符串,例如*搜索 4 个连续数字的序列*。*
在下面的例子中,我们导入了包含 Python 正则表达式功能的`re`模块,然后用我们的正则表达式(`\d\d\d\d`)和我们想要搜索的字符串调用`search`函数:
```py
>>> import re
>>> str = 'Today is 31 May 2012.'
>>> mo = re.search(r'\d\d\d\d', str)
>>> print(mo)
<_sre.SRE_Match object at 0x01D3A870>
>>> print(mo.group())
2012
>>> print('%s %s' % (mo.start(), mo.end())
16 20
```
在一个正则表达式中,`\d`表示*任意位*,所以`\d\d\d\d`表示*任意位,任意位,任意位*,或者说白了,*连续 4 位*。正则表达式大量使用反斜杠,反斜杠在 Python 中有特殊的含义,所以我们在字符串前面加了一个 *r* 使其成为一个*原始字符串*,它阻止 Python 以任何方式解释反斜杠。
如果`re.search`找到匹配我们正则表达式的东西,它返回一个*匹配对象*,其中保存了关于匹配内容的信息。在上面的例子中,我们打印出匹配的子字符串,以及它在被搜索的字符串中的开始和结束位置。
注意 Python 没有匹配日期(`31`)。它会匹配前两个字符,即与前两个`\d`匹配的 *3* 和 *1* ,但是下一个字符(一个空格)不会匹配第三个`\d`,所以 Python 会放弃并继续搜索字符串的剩余部分。
## 匹配一组字符
让我们试试另一个例子:
```py
>>> str = 'Today is 2012-MAY-31'
>>> mo = re.search(r'\d\d\d\d-[A-Z][A-Z][A-Z]-\d\d', str)
>>> print(mo.group())
2012-MAY-31
```
这一次,我们的正则表达式包含新元素`[A-Z]`。方括号表示*与其中一个字符*完全匹配。例如,`[abc]`表示 Python 将匹配一个 *a* 或 *b* 或 *c* ,但不匹配其他字母。由于我们想要匹配 *A* 和 *Z* 之间的任何字母,我们可以写出整个字母表(`[ABCDEFGHIJKLMNOPQRSTUVWXYZ]`)但是谢天谢地Python 允许我们使用连字符(`[A-Z]`)来缩短这个字母。所以,我们的正则表达式是`\d\d\d\d-[A-Z][A-Z][A-Z]-\d\d`,意思是:
* 寻找(或*匹配*)一个数字(4 次)。
* 匹配一个'-'字符。
* 匹配 A 和 Z 之间的一个字母(三次)。
* 匹配一个'-'字符。
* 匹配一个数字(2 次)。
如上例所示Python 找到了嵌入在字符串中的日期。
不幸的是,我们的正则表达式目前只处理大写的月份名称:
```py
# The month uses lower-case letters
>>> str = 'Today is 2012-May-31'
>>> mo = re.search(r'\d\d\d\d-[A-Z][A-Z][A-Z]-\d\d', str)
>>> print(mo)
None
```
有两种方法可以解决这个问题。我们可以传入一个标志,表示搜索应该不区分大小写:
```py
>>> str = 'Today is 2012-May-31'
>>> mo = re.search(r'\d\d\d\d-[A-Z][A-Z][A-Z]-\d\d', str, re.IGNORECASE)
>>> print(mo.group())
2012-May-31
```
或者,我们可以扩展字符集来指定更多的字符:`[A-Za-z]`表示*大写 A 到大写 Z或者小写 A 到小写 Z* 。
```py
>>> str = 'Today is 2012-May-31'
>>> mo = re.search(r'\d\d\d\d-[A-Za-z][A-Za-z][A-Za-z]-\d\d', str)
>>> print(mo.group())
2012-May-31
```
## 正则表达式的重复
上一个例子中的正则表达式开始变得有点笨拙,所以让我们看看如何简化它。
在正则表达式中,`{n}`(其中 n 是数字)表示*重复前面的元素 n 次*。所以我们可以重写这个正则表达式:
```py
\d\d\d\d-[A-Za-z][A-Za-z][A-Za-z]-\d\d
```
变成这样:
```py
\d{4}-[A-Za-z]{3}-\d{2}
```
这意味着:
* 匹配任意数字(4 次)。
* 匹配一个'-'字符。
* 匹配字母 A-Z 或 a-z (3 次)。
* 匹配一个'-'字符。
* 匹配任意数字(2 次)。
这就是它的作用:
```py
>>> str = 'Today is 2012-May-31'
>>> mo = re.search(r'\d{4}-[A-Za-z]{3}-\d{2}', str)
>>> print(mo.group())
2012-May-31
```
当指定应该匹配多少重复时,我们有很大的灵活性。
* 我们可以指定一个范围,例如`{2,4}`表示*匹配 2 - 4 次重复*。
```py
>>> str = 'abc12345def'
>>> mo = re.search(r'\d{2,4}', str)
>>> print(mo.group())
1234
```
* 我们可以忽略上限值,例如`{2,}`表示“匹配 2 次或更多次重复”。
```py
>>> str = "abc12345def"
>>> mo = re.search(r'\d{2,}', str)
>>> print(mo.group())
12345
```
# 常见重复的简写
有些类型的重复非常常见,它们有自己的语法。
* `{1,}`表示*匹配前一个元素一次或多次*,但这也可以用特殊的`+`操作符来写(例如`\d+`)。
```py
>>> str = 'abc12345def'
>>> mo = re.search(r'\d+', str)
>>> print(mo.group())
12345
```
* `{0,}`表示*匹配前一个元素零次或多次*,但这也可以用`*`操作符来写(如`\d*`)。
```py
>>> str = 'abc12345def'
>>> mo = re.search(r'\d*', str)
>>> print(mo.group())
```
呀,发生什么事了?!为什么这个什么都没打印出来?嗯,你必须小心*操作符,因为它会匹配*零*或更多的重复。在这种情况下Python 看着被搜索字符串的第一个字符,对自己说*这是数字吗?没有。我匹配了零个或多个数字吗?是(零),所以正则表达式已经匹配了。*如果我们看看`MatchObject`告诉我们的:
```py
>>> print('%s %s' % (mo.start(), mo.end()))
0 0
```
我们可以看到这正是所发生的,它在被搜索的字符串的最开始匹配了一个空的子字符串。让我们稍微改变一下我们的正则表达式:
```py
>>> str = 'abc12345def'
>>> mo = re.search(r'c\d*', str)
>>> print(mo.group())
c12345
```
现在我们的正则表达式说*匹配字母 c然后零个或多个数字*,这就是 Python 随后找到的。
* `{0,1}`表示*匹配前一个元素 0 或 1 次*,但这也可以用`?`操作符来写(如`\d?`)。
```py
>>> str = 'abc12345def'
>>> mo = re.search(r'c\d?', str)
# Note: the \d was matched 1 time
>>> print(mo.group())
c1
>>> mo = re.search(r'b\d?', str)
# Note: the \d was matched 0 times
>>> print(mo.group())
b
```
在下一篇文章中,我们将继续讨论正则表达式的更多高级用法。