Python2.7 - Regular Expression HOWTO 学习小结

说明

  • 是基于官方的how to文档来学习的,并不是全面的说明,也不是翻译。更像是随笔记录。
    https://docs.python.org/2/howto/regex.html
  • 正则表达式的基本语法在维基页面有完整的
    https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F

基本的用法

  • end() 的值是第一个不匹配的字符的下标,而不是最后一个匹配字符的下标
  • match()是从第一个字符开始匹配的,而不是可以从中间搜索;查到中间匹配的是search()。
p1 = re.compile('[a-z]+')
m1 = p1.match( '...tem1po' )
m2 = p1.match( 'tem1po' )

上面这段代码中:
m1的值是None,原因是match是从第一个字符开始匹配,而第一个字符无法匹配;
m2的值是'tem',原因是第一个字符是匹配的,然后一直到1这个不匹配的字符终止

  • search()只匹配到第一个匹配字符即终止
    跟match()相比较区别在于,match()是从第一个字符开始匹配,而search()是寻找到第一个可以匹配的字符。但是匹配模式加上^的话,效果是一样的。
p1 = re.compile('^[a-z]+')
m1 = p1.search( '...tem1po2asdasd' )
p2 = re.compile('[a-z]+')
m2 = p2.match( '...tem1po2asdasd' )

上面的例子中m1和m2的效果是一样的,都是None

  • raw字符串
    跟""有关系,对于re的compile来说,\是特殊字符。如果要匹配一个字符串中的空格边界,则需要输入\b才可以匹配上。
    如果使用raw字符串,就是在字符串前面加上r,则只需要输入\b即可。
p = re.compile('\.class\\b', 'no .class at all')
p1 = re.compile(r'\.class\b', 'no .class at all')

上面的例子中,P和P1是等效的。但是.为什么不受影响,没有想明白。
不管怎样,所有的pattern字符串加上r就好了

group & groups

还是拿代码直接演示比较好

  • 例子1
    代码:跟原始的示例代码相比,将固定的b修改成不同的数字
p = re.compile('(a[0-9]c)*')
m = p.match('a1ca2ca3c')
print m.group()
print m.group(0)
print m.group(1)
print m.groups()

运行结果

a1ca2ca3c
a1ca2ca3c
a3c
('a3c',)

从这个结果可以看到:

  1. 匹配的原意是a和b中间夹一个数字,并且多次匹配,所以最终的匹配结果是整个字符串
  2. group()的结果和group(0)的结果是一样的
  3. 由于只有一对(),所以group的下标只到1,其输出结果就是捕获的一对();不过让人意外的是,输出的是最后一对,而不是第一对。
  4. groups()输出的是个元组,只存放捕获的();值就是group(0)以后的数据
  • 例子2
    代码:跟原始的例子相比,多了1对额外的(),并且将所有的group下标都打印出来
p = re.compile(r'(((a)[0-9])c)*(dfg)')
m = p.match('a1ca2ca3cdfg')
print m.group()
print m.group(0)
print m.group(1)
print m.group(2)
print m.group(3)
print m.group(4)
print m.groups()

运行结果

a1ca2ca3cdfg
a1ca2ca3cdfg
a3c
a3
a
dfg
('a3c', 'a3', 'a', 'dfg')

这个例子跟第一个例子比起来:

  1. 有1对()变成了4对。其中左边的是3对嵌套的(),右边是单独的一对()
  2. 从输出的结果看,先左后右,先外后里的进行匹配输出
  • 例子3

代码1

p = re.compile(r'((\b\w+)\.+)\.+')
m = p.search('Paris..in..the..the..spring')
print m.group()
print m.group(0)
print m.group(1)
print m.group(2)
print m.groups()

运行结果1

Paris..
Paris..
Paris.
Paris
('Paris.', 'Paris')
  • **代码2 **
p = re.compile(r'((\b\w+)\.+)\.+\0')
m = p.search('Paris..in..the..the..spring')
print m.group()
print m.group(0)
print m.group(1)
print m.group(2)
print m.groups()

运行结果2

Traceback (most recent call last):
  File "testPython/test_main.py", line 7, in 
    print m.group()
AttributeError: 'NoneType' object has no attribute 'group'
  • 代码3
p = re.compile(r'((\b\w+)\.+)\.+\1')
m = p.search('Paris..in..the..the..spring')
print m.group()
print m.group(0)
print m.group(1)
print m.group(2)
print m.groups()

运行结果3

the..the.
the..the.
the.
the
('the.', 'the')
  • 代码4
p = re.compile(r'((\b\w+)\.+)\.+\2')
m = p.search('Paris..in..the..the..spring')
print m.group()
print m.group(0)
print m.group(1)
print m.group(2)
print m.groups()

运行结果4

the..the
the..the
the.
the
('the.', 'the')

说明

  1. 代码1是没有添加\1,就是常规的匹配。主要是查看常规匹配下的group的值
  2. 代码2是加上了\0,也就是group(0)。但是似乎不允许这样,group(0)实质就是group()本身。
    这样做似乎产生了一个递归,所以匹配结果就是None。第一次匹配结果是the..,加上\0后,\0本身的结果就变成了the..the..,然后再加一次本身。这样也能去解释匹配为0.
    仔细去观察4段代码的运行结果,gourp(1)和group(2)的结果一直都是一样的。
  3. 代码3和代码4,因为原始的匹配就是有2对(),所以可以使用不同的下标对应不同的。

Non-capturing and Named Groups

  • (?:...)
    这个符号的作用是,不取回...对应的值,放入group中。也就是说匹配上之后,根本就没有group。
p = re.compile(r'(?:[abc])+')
m = p.search('abc')
print m.group()
print m.group(0)
print m.group(1)
print m.groups()

上面这段代码的运行结果是在group(1)的时候抛出异常,因为这种模式下没有group。运行结果如下:

abc
abc
Traceback (most recent call last):
  File "test_main.py", line 9, in 
    print m.group(1)
IndexError: no such group

如果我们去掉group(1)的打印,只打印出groups的话,就能看出没有group。代码如下:

p = re.compile(r'(?:[abc])+')
m = p.search('abc')
print m.group()
print m.group(0)
print m.groups()

运行结果是:

abc
abc
()

可以明显的看出groups是没有任何值的。

  • (?P...)
    这个是Python特定的一个扩展实现,其实就是给...表达式匹配的值设定了一个key值,然后可以使用(?P=name)来调用之前表达式匹配的值。看下面的例子,就能理解了。
p = re.compile(r'(?P\b\w+)\s+(?P=word)')
m = p.search('Paris in the the spring')
print m.group()
print m.group(0)
print m.group(1)
print m.groups()

上面的代码运行结果是:

the the
the the
the
('the',)

仔细观察表达式,并根据原理,我们可以看到(?P=word)等同于group的\1回调。\1是在group中找到1为下标的值,而(?P=word)则是通过word这个key值找到对应的匹配值。

  • (?=...)
    这个看起来和(?:...)很像,但是其实不是一回事。还是用例子说话吧
print u"============= 例子1  =============="
m = re.search(r"(?:[abc])+", "abc")
print m
print m.group()
print m.groups()
print u"============= 例子2  =============="
m = re.search(r"(?=[abc])+", "abc")
print m
print m.group()
print m.groups()
print u"============= 例子3  =============="
m = re.search(r"(?=[abc])+", "1abc")
print m
print m.group()
print m.groups()
print u"============= 例子4  =============="
m = re.search(r"\d(?=[abc])+", "1abc")
print m
print m.group()
print m.groups()
print u"============= 例子5  =============="
m = re.search(r"\d(?:[abc])+\d", "1abc2")
print m
print m.group()
print m.groups()
print u"============= 例子6  =============="
m = re.search(r"\d(?=[abc])+\d", "1abc2")
print m
print u"============= 例子7  =============="
m = re.search(r"\d(?=[abc])+", "1abc2")
print m
print m.group()
print m.groups()
print u"============= 例子8  =============="
m = re.search(r"\d(?=[abc])+", "abc")
print m

运行结果是:

============= 例子1  ==============
<_sre.SRE_Match object at 0x103674370>
abc
()
============= 例子2  ==============
<_sre.SRE_Match object at 0x1036743d8>  
结果为空
()
============= 例子3  ==============
<_sre.SRE_Match object at 0x103674370>
结果为空
()
============= 例子4  ==============
<_sre.SRE_Match object at 0x1036743d8>
1
()
============= 例子5  ==============
<_sre.SRE_Match object at 0x10e9133d8>
1abc2
()
============= 例子6  ==============
None
============= 例子7  ==============
<_sre.SRE_Match object at 0x10e9133d8>
1
()
============= 例子8  ==============
None

上面的运行结果,可以得出如下的内容:

  1. (?=...)的匹配结果也是不会记录在group中的
  2. 例子2的结果,说明其实是匹配上了的,但是奇怪的是group()的结果却没有匹配值
  3. 例子3就在要匹配的字符串前加上数字1,发现还是没有输出结果
  4. 最后修改了表达式,在之前增加了\d以匹配例子3增加的数字1,结果发现是有输出的,输出结果就是\d匹配的1。
  5. 看到例子4之后,就在考虑如果后面增加匹配呢?
    首先例子5在+后面增加了一个\d的匹配,并在比对的字符串中再最后增加了2。例子5用:替换了=,从输出结果看,匹配是成功的。
  6. 例子6将例子5的:再替换回=,我们发现匹配是失败的,返回的对象是None
  7. 在例子7中,我们将匹配的表达式中在例子5增加的哪个/d再取消,匹配成功,返回值跟例子4一样。
  8. 例子把是在例子4基础上做修改,将比对的字符串的1去掉。从结果看,可以确定是先匹配整个表达式,而不仅仅是先匹配...

综合上面的实例,我们可以对(?=...)得出如下总结:
** 1. 此匹配表达式只能放在最后,针对()单元的+等重复标识除外**
** 2. 首先是匹配整个表达式,而不仅仅是...。在例子8中,就是先匹配r"\d(?=[abc])+"。 ** 3. 当整个表达式匹配成功后,其会找到...的前一个表达式,再从...匹配的字符往前看其前一个表达式的匹配的值,并作为最后输出。如果没有,则为空。在我们的例子中前一个表达式是/d,匹配的值是1`。**

Modifying Strings

  • ** split()**
    1. Split的参数是分割的标识,还可以加上最多分成几组的参数。
    2. 分割的结果是,除开被匹配上的字符,其余的字符组成结果
    3. 结果的最后一个,固定是空字符串
    4. 比较特殊的是,分割匹配包含在()中的话,输出的结果也包含匹配的字符串。
      直接引用官方文档的例子,就完全可以说明了
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
  • sub() & subn()
    sub和subn的区别在于,subn会输出执行替换的字符串的个数。其余的直接看官方列出的示例代码即可看明白。
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)

有一个点是需要注意的,需要注意*号。先看下面的例子:

>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b-d-'

上面例子中,由于*代表是可以0次,所以跟x隔开的空的地方,都被替换成了要替换的值。
如果是+,表示至少有一次时,上面的例子替换就显得正常了,见下面代码

>>> p = re.compile('x+')                     
>>> p.sub('-', 'abxd')
'ab-d'
  • 可以在替换函数中使用group下标 或者 p
    直接看官方的代码示例就清楚了
>>> p = re.compile('section{ (?P [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g}','section{First}')
'subsection{First}'
  • 可以在替换函数中调用其他函数
    下面是官方的代码,看起来有些递归的味道。函数的参数是match对象,而这个match对象实际上就是调用其函数的对象。
>>> def hexrepl(match):
...     "Return the hex string for a decimal number"
...     value = int(match.group())
...     return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'

Compilation Flags

  • IGNORECASE
    就是在匹配的时候不在乎大小写
    代码:
print u"============= 例子1  =============="
m = re.search(r"[abc]+", "aBc", re.IGNORECASE)
print m
print m.group()

运行结果:

============= 例子1  ==============
<_sre.SRE_Match object at 0x101eb8e68>
aBc
  • LOCALE
    根据官方文档的说法,\w只能匹配英文,类似于匹配了[a-zA-Z]。使用LOCALE则可以匹配更多本地字符。
    尝试了一下中文,似乎不行。由于没有其他例子,举得是法语字符,后续需要时再研究把。
  • MULTILINE
    ^$分别是从行首和行尾进行匹配,但是这个是没法跨行的。使用MULTILINE可以使得这两个跨行。
    代码:
print u"============= 例子1  =============="
m = re.findall(r"^[123]", "2abc\n1d")
print m
print u"============= 例子2  =============="
m = re.findall(r"^[123]", "2abc\n1d", re.MULTILINE)
print m

运行结果:

============= 例子1  ==============
['2']
============= 例子2  ==============
['2', '1']

从例子中,明显可以看出来,加了MULTILINE参数后,在新的一行^仍旧起了作用。

  • DOTALL
    配置之后,可以匹配上换行符;不配置,也就不能匹配。
    代码:
print u"============= 例子1  =============="
m = re.findall(r".+", "2abc\n1d")
print m
print u"============= 例子2  =============="
m = re.findall(r".+", "2abc\n1d", re.DOTALL)
print m

运行结果:

============= 例子1  ==============
['2abc', '1d']
============= 例子2  ==============
['2abc\n1d']
  • ** UNICODE**
    直接贴原文吧,涉及到再研究
Make \w, \W, \b, \B, \d, \D, \s and \S dependent on the Unicode character properties database.
  • ** VERBOSE**
    这个主要是为了方便阅读。
    不用此参数时的写法:
pat = re.compile(r"\s*(?P
[^:]+)\s*:(?P.*?)\s*$")

用了VERBOSE参数,则可以写成下面的样子,可以添加注释方便阅读。

pat = re.compile(r"""
 \s*                 # Skip leading whitespace
 (?P
[^:]+) # Header name \s* : # Whitespace, and a colon (?P.*?) # The header's value -- *? used to # lose the following trailing whitespace \s*$ # Trailing whitespace to end-of-line """, re.VERBOSE)

你可能感兴趣的:(Python2.7 - Regular Expression HOWTO 学习小结)