正则表达式与Python(二更 2018 3.7 18:07)

正则表达式

自己去网上查,这里不教。

re模块:核心函数和方法

使用compile()函数编译正则表达式

使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象。

推荐预编译,但并不是必须的。

如果需要编译,就是用编译后的方法

如果不需要编译,就使用函数

幸运的是,无论是使用函数还是使用方法,它们的名字都是相同的。

可选标记

重用的模块属性:

属性(标记) 作用
re.I re.IGNORECASE 不区分大小写的匹配
re.L re.LOCAL 根据所使用的本地语言环境通过\w、\W、\b、\B、\s、\S实现匹配
re.M re.MULTILINE ^和$分别匹配目标字符串中行的起始和末尾,而不是严格匹配整个字符串本身的起始和末尾
re.S re.DOTALL “.”(点号)通常匹配除了\n(换行符)之外的所有单个字符;该标记表示“.”(点号)能够匹配全部字符
re.X re.VERBOSE 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标志被指定时,在 RE 字符串中的空白符被忽略,除非该空白符在字符类中或在反斜杠之后;这可以让你更清晰地组织和缩进 RE。它也可以允许你将注释写入 RE,这些注释会被引擎忽略;注释用 “#”号 来标识,不过该符号不能在字符串或反斜杠之后。

关于这些标记后面会详细解释。

匹配对象以及group()和groups()方法

除了正则表达式对象,还有一个对象类型:匹配对象。这些是成功调用match()或者search()返回的对象。

匹配对象有两个主要的方法:group()groups()

group(num=0)

要么返回整个匹配对象,或者编号为 num 的子组。

groups(default=None)

返回一个包含所有匹配子组的元组(如果没有成功匹配,则返回一个空元组)

groupdict(default=None)

返回一个包含所有匹配的命名子组的字典,所有的子组名称为键(匹配失败,返回空字典)

使用match(pattern, string, flags=0)方法匹配字符串

尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象;如果失败,则返回None。

运用match()group()的实例:

import re

m = re.match('foo', 'foo')
if m:
    print(m.group())

输出:

foo

if语句避免了匹配失败后抛出的AttributeError异常。

使用search()在一个字符串中查找模式(搜索与匹配的对比)

工作方式和match()完全一致,不同之处在于search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。

例:

# 匹配失败
m = re.match('foo', 'afoo') 

# 搜索成功
m = re.search('foo', 'afoo') 

分组

直接上示例:

import re

patt = '(\w\w\w)-(\d\d\d)'
m = re.match(patt, 'abc-123')
if m:'
    print(m.group())
    print(m.group(1))
    print(m.group(2))

输出:

abc-123
abc
123

可见,group()通常用于以普通方式显示所有的匹配部分,但也能用于获取各个匹配的子组。可以使用groups()方法来获取一个包含所有匹配子字符串的元组。

而且,要注意,用group()获取子组时,数字从1开始。

原始字符串

反斜线有转义的功能,\n表示换行符,如果打印一个路径,例如:

>>> print 'C:\nowhere'
# 打印结果如下
C:
owhere
# 我们可以通过反斜线进行转义,改为:
>>> print 'C:\\nowhere'

复制代码
但是如果对于长路径,那么需要很多的反斜线,这样原始字符串就派上用场了。

用法:在字符串前加上字母r

原始字符不会把反斜线当作特殊字符串。

>>> print(r'C:\nowhere')
C:\nowhere

>>> print(r'C:\Program Files\fnord\foo\bar\baz\frozz\bozz')
C:\Program Files\fnord\foo\bar\baz\frozz\bozz

当然我们也要像平常一样对引号进行转义,但是,最后的输出的字符串也包含了转义所用的反斜线

>>> print(r'Let's go)
SyntaxError: invalid syntax
>>> print(r'Let\'s go')
Let\'s go

但是不能在原始字符串结尾输入反斜线。

print(r"This is illegal\")

上面写法会报错,参照上一个范例这是一个显而易见的结论。最后一个字符是反斜线,Python就不清楚是否应该结束字符串。

但如果字符串最后一个字符确实是\,可以使用一个技巧解决上述问题

>>> print(r'C:\Program Files\foo\bar' '\\')

C:\Program Files\foo\bar\

此处知识点转载自Python基础语法。

findall(pattern, string, flags=0)

查找字符串中所有(非重复)出现的正则表达式模。如果匹配成功,则返回匹配对象;如果失败,则返回None。

finditer(pattern, string, flags=0)

与findall()相同,但返回一个迭代器,对于每一次匹配,迭代器都返回一个匹配对象。

使用findall()和finditer()查找每一次出现的位置

>>> re.findall('foo', 'foo')
['foo']
>>> re.findall('foo', '?foo')
['foo']
>>> re.findall('foo', '?foo!foo#foo')
['foo', 'foo', 'foo']

当使用元组时,对于每一个成功的匹配,每个子组匹配是由findall()返回的结果列表中的单一元素;

对于多个成功的匹配(即有多个子组时),每个子组匹配使返回的一个元组中的单一元素,而且每个元组(每个元组都对应一个成功的匹配)是结果列表的元素。

通过示例来加强理解吧:

>>> s = 'This and that.'
>>> re.findall(r'(th\w+) and (th\w+)', s, re.I)
[('This', 'that')]

>>> for match in re.finditer(r'(th\w+) and (th\w+)', s, re.I):
        print(match.group())
This and that

简单的说吧,就是finditer返回了一个可调用的对象,使用 for i in finditer()的形式,可以一个一个的得到匹配返回的匹配对象。这在对每次返回的对象进行比较复杂的操作时比较有用。

更多例子:

import re

s = 'This and that. These and those.'
result = re.finditer(r'(th\w+) and (th\w+)', s, re.I)

for match in result:
    print(match.group())

输出:

This and that
These and those

至于匹配对象的操作(group()groups()之类),就是之前的知识了,这里主要理解finditer()函数返回的迭代器是什么意思。

高级操作:

import re

s = 'This and that. These and those.'
result = [r.groups() for r in re.finditer(r'(th\w+) and (th\w+)', s, re.I)]

print(result)

输出:

[('This', 'that'), ('These', 'those')]

使用sub()和subn()搜索与替换

两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。用来替换的部分通常是一个字符串。

不同的是,subn()还返回一个表示替换总数的数字,和替换后的字符串一起组成一个有两个元素的元组返回。

例:

>>> re.sub(r'\d', '!', 'asdf1ghj2p')
asdf!ghj!p

>>> re.subn(r'\d', '!', 'asdf1ghj2p')
('asdf!ghj!p', 2)

分组(进阶)

分组时,还可以使用\N,其中 N 是在替换字符串中使用的分组编号,从1开始的数字。

从左到右,第一个分组的编号是1,第二个是2,以此类推。

举例加以理解:

将美式的日期表示法: MM/DD/YY 换成 DD/MM/YY

>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '1/12/1998')
12/1/1998

一共分了三个组,从左到右,从1开始依次编号。

在限定模式上使用split(pattern, string, max=0)分隔字符串

基于正则表达式的模式分隔字符串,为字符串分隔功能添加一些额外的能力。

若不想为每次模式的出现都分割字符串,就可以通过为max参数设定一个非零值来指定最大分割数。

对于不需要使用特殊符号或特殊条件来分割的字符串,re.split()str.split()工作方式相同。

>>> re.split(',' ,'1,2,3')
['1', '2', '3']

但如果情况更复杂的话,如,一个用于Web站点的简单解析器,用户可以输入城市和州名,或者城市名加上ZIP编码,或者三者同时输入。

import re


DATA = (
    'Mountain View, CA 94040',
    'Sunnyvale, CA',
    'Los Altos, 94023',
    'Cupertino 95014',
    'Palo Alto CA',
)

for datum in DATA:
    print(re.split(r', | (?=(?:[A-Z]{2}|\d{5}))', datum))

输出:

['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']

这里运用了正向前视断言(?=),后面会详细的讲。

解释起来就是:如果一个空格后面紧挨着2个大写字母或者5个数字的话,就可以作为分隔符。

秀不秀?

正则表达式与Python(二更 2018 3.7 18:07)_第1张图片

扩展符号

通过使用(?Lmsux)系列选项,用户可以直接在正则表达式里面指定一个或多个标记,而不是通过compile()或者其他re模块函数。

之前我们指定标记时用的是参数的方法,这次可以直接写在正则表达式里面。

之前的re.I/IGNORECASE等,只要把那个简写版的大写字母变成小写字母写成这样(?i)如果有多个标记就都写在一起(?im)

接下来将展示各个标记的用法及示例。

re.I/IGNORECASE

>>> result = re.findall(r'(?i)OK', 'Ok, oK, ok, OK.')
['Ok', 'oK', 'ok', 'OK']

re.M/MULTILINE

import re


result = re.findall(r'(?im)(^th[\w ]+)', """
This line is the first,
another line ,
that line is the last.""")
print(result)

输出:

['This line is the first', 'that line is the last']

通过使用“多行”标记,能够在目标字符串中跨行匹配,而不是视整个字符串为整体。

比如,此时忽略了“the”,因为它不在行首。

re.S/DOTALL

该标记表明(.)能够用来表示\n符号:

不用标记时:

import re

result = re.findall(r'th.+', """
the first line
the second line
the third line""")
print(result)

输出:

['the first line', 'the second line', 'the third line']

使用标记时:

import re


result = re.findall(r'(?s)th.+', """
the first line
the second line
the third line""")
print(result)

输出:

['the first line\nthe second line\nthe third line']

re.X/VERBOSE

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)

b = re.compile(r"\d+\.\d*")

第一个例子是用了re.VERBOSE标志的,有了注释,浅显易懂。

第二个例子里,Python 的字符串自动连接可以用来将 RE 分成更小的部分,但它比用 re.VERBOSE 标志时更难懂。

(?: )

通过使用该符号,可以对正则表达式进行分组,但是并不会保存该分组用于后续的检索或应用。

当不想保存今后永远不会使用的多余匹配时,这个符号就非常有用。

import re

result = re.findall(r'http://(?:\w+\.)*(\w+\.com)', "http://www.google.com"
                                                    " http://google.com "
                                                    "http://code.google.com")
print(result)

输出:

['google.com', 'google.com', 'google.com']

第一个分组只是为了方便后面的“*”而已,不需做保存,所以用(?:)

(?P< name >)

通过一个名称标识符而不是使用从1开始增加到N的增量数字来保存匹配。

>>> re.search(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})',
                   '(800) 555-1212').groupdict()
{'areacode': '800', 'prefix': '555'}

>>> re.search(r'\((\d{3})\) (\d{3})-(?:\d{4})',
                   '(800) 555-1212').groupdict()
{}

我们最后用了一个groupdict()将匹配结果转换为字典,每个键就是每个组的名称。

如果没有指定名称输出就是空字典。

之前说过分组后可以使用\N来检索,命名后可以使用\g的形式来检索:

>>> re.sub(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})',
                '(\g) \g-xxxx'
                ,'(800) 555-1212')
                n
'(800) 555-xxxx'

你可能感兴趣的:(python,python,正则表达式)