2.Python3标准库--文本

(一)string:文本常量和模板

1.函数

1
2
3
4
5
6
7
8
9
10
import string
'''
string模块在最早的Python版本中就已经有了。以前这个模块中提供的很多函数已经移植到str对象中,不过这个模块仍然保留了很多有用的常量和类来处理str对象
'''
 
# 函数capwords会把一个字符串中的所有单词的首字母变成大写
= "when i was young, i'd listen to the radio"
print(s)  # when i was young, i'd listen to the radio
print(string.capwords(s, sep=" "))  # When I Was Young, I'd Listen To The Radio
# 这段代码的结果等同于先调用split,把结果中的单词首字母大写,然后调用join来合并结果。sep可以省略,默认为空格

  

2.模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import string
 
 
'''
字符串模板是PEP292当中新增的部分,将作为内置拼接语法的替代做法。使用string.Template拼接变量时,要在名字前加前缀$来标识变量(例如$var)。
或者,如果有必要区分变量和周围的文本,可以使用大括号包围变量,如${var}
'''
# 先看看str.format这种常规做法
values = {"var""foo"}
= "var: %(var)s, escape: %%, %(var)sxxx"
print("result ->", s % values)  # result -> var: foo, escape: %, fooxxx
# 注意到中间的escape: %%,这种触发字符要想让其失去效果,只当做普通字符来处理的话,要重复两次来进行转义
 
 
# 下面来使用模板,可以看到最后一个var加上了{},因为var和后面的文本黏在一起了
= "var: $var, escape: $$, ${var}xxx"
# 第一步:先得到可以用来渲染的模板
= string.Template(s)
# 第二步:进行替换,会有一个返回值,就是我们替换之后的结果
print(t.substitute(values))  # var: foo, escape: $, fooxxx
print(t.substitute(**values))  # var: foo, escape: $, fooxxx
# 可以看到var都被替换成了foo。但是注意到我们传入value(一个字典),和**value(var=foo关键字参数)得到的结果是一样的。
# 因为string的模板不像flask,tornado等框架的模板一样,支持逻辑上的运算或者数据结构上的变换。
# 如果在jinja2中,我传入字典的话,那么在模板中还可以进行取值,但是string中的模板不支持,只支持字符间的替换。
 
 
# 我们来看看jinja2
import jinja2
= "{{var}}--{{dic.get('key', 'value')}}--{{dic.get('KEY', 'value')}}"
= jinja2.Template(s)
print(t.render(var="foo", dic={"key""mashiro"}))  # foo--mashiro--value
# 可以看到jinja2是支持字典的取值,由于dic中没有"KEY"这个键,那么获取默认值。但是string.Template是不支持的。
# 因此对于string.Template来说,传入关键字参数和字典是一样的,传入字典会自动将字典给打开,根据k,v进行替换
 
 
# 此外对于字符串的模板还有一个安全的用法
= "$var--$missing"
= string.Template(s)
# 我这里没有传入missing,因此如果使用substitute则会报错,但如果是safe_substitute的话会自动忽略,只对传入的进行替换
# 那如果我多穿了一个,比如说s中并没有$xxx,那么会怎么样呢?
print(t.safe_substitute(var="foo", xxx="xxxxx"))  # foo--$missing
# 显然没有任何问题,因此可以总结一下
# s中定义了,但是模板替换的时候没有传相应的值,那么substitute会报错,safe_substitute不会
# 但是s中没有定义,比如$xxx,而我们却多传了,那么substitute和safe_substitute都不会报错,会自动忽略

  

3.高级模板

1
2
3
4
5
6
'''
可以调整string.Template在模板中查找变量名所使用的正则表达式模式,以改变它的默认语法。
我们可以修改delimiter和idpattern属性。
因此最简单的方法就是自己定义一个类,继承自string.Template,然后重写这两个属性。
但是说实话,这个不常用,因此不再详细介绍。
'''

  

4.Formatter

1
2
3
4
5
'''
string.Formatter类实现了与str的format()方法相同的布局规范。
特性包括类型强制转换,对齐,属性,和域引用,命名和位置模板参数以及类型特定的格式化选项。
大部分情况下,使用format()已经足够,而且会更加方便,但也可以通过Formatter构建子类,实现更复杂的效果(基本不用)
'''

  

5.常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import string
 
 
'''
string模块包括大量与ASCII和数值字符集相关的常量,都可以通过string这个模块直接获取
 
whitespace = ' vf'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace
'''

  

(二)textwrap:格式化文本段落

1.示例数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import textwrap
 
'''
需要美观打印(pretty-printing)的情况下,可以使用textwrap模块格式化要输出的文本。
它提供了很多文本编辑器和字符处理器中都有的段落自动换行或填充特性
'''
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''

      

2.填充段落

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
# fill函数取文本作为输入,生成格式化文本作为输出
print(textwrap.fill(text, width=50))
'''
     There are moments in life when you miss
someone      so much that you just want to pick
them      from your dreams and hug them for real!
Dream what      you want to dream;go where you
want to go;     be what you want to be,because you
have      only one life and one chance to do all
the things you want to do.
'''
 
# 结果不是太让人满意。文本虽然已经对齐,不过只有第一行保留了缩进,后面各行的空格则嵌入在段落中

  

3.去除现有的缩进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
 
# 关于刚才的例子,其输出中混合嵌入了制表符和额外的空格,所以格式不是太美观。
# 用dedent可以去除示例文本中所有的行前面的空白符,这会生成更好的结果
# 并且允许在Python代码中直接使用docstring或者内嵌的多行字符串,同时去除代码本身的格式。
print(textwrap.dedent(text).strip())
'''
There are moments in life when you miss someone
so much that you just want to pick them
from your dreams and hug them for real! Dream what
you want to dream;go where you want to go;
be what you want to be,because you have
only one life and one chance to do all the things you want to do.
'''
# 可以看到dedent作用就是把每一行开头的缩进给去掉

  

4.结合dedent和fill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
 
dedent_text = textwrap.dedent(text).strip()
print(textwrap.fill(dedent_text, width=60))
'''
There are moments in life when you miss someone  so much
that you just want to pick them  from your dreams and hug
them for real! Dream what  you want to dream;go where you
want to go; be what you want to be,because you have  only
one life and one chance to do all the things you want to do.
'''

  

5.缩进块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
# 可以使用indent函数为一个字符串的所有行增加一致的前缀文本
dedent_text = textwrap.dedent(text).strip()
final_text = textwrap.indent(dedent_text, ">>>")
print(final_text)
'''
>>>There are moments in life when you miss someone
>>>so much that you just want to pick them
>>>from your dreams and hug them for real! Dream what
>>>you want to dream;go where you want to go;
>>>be what you want to be,because you have
>>>only one life and one chance to do all the things you want to do.
'''
 
# 除此之外还可以给指定行添加
final_text = textwrap.indent(dedent_text, prefix="->", predicate=lambda line: len(line.strip()) > 40)
print(final_text)
'''
->There are moments in life when you miss someone
so much that you just want to pick them
->from your dreams and hug them for real! Dream what
->you want to dream;go where you want to go;
be what you want to be,because you have
->only one life and one chance to do all the things you want to do.
'''
# 显然lambda里面的line就是每一行的文本,指定文本长度大于40的

  

6.悬挂缩进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
# 不仅可以设置输出的宽度,还可以采用同样的方式单独控制首行的缩进,使首行的缩进不同于后续的各行
dedent_text = textwrap.dedent(text).strip()
print(textwrap.fill(dedent_text,
                    initial_indent="",
                    subsequent_indent=" "*4,
                    width=50))
'''
There are moments in life when you miss someone
    so much that you just want to pick them  from
    your dreams and hug them for real! Dream what
    you want to dream;go where you want to go; be
    what you want to be,because you have  only one
    life and one chance to do all the things you
    want to do.
'''
# 这便可以生成悬挂缩进,即首行缩进小于其他行的缩进
# 缩进值也可以包含非空白字符。
print(textwrap.fill(dedent_text,
                    initial_indent="",
                    subsequent_indent="*"*4,
                    width=50))
'''
There are moments in life when you miss someone
****so much that you just want to pick them  from
****your dreams and hug them for real! Dream what
****you want to dream;go where you want to go; be
****what you want to be,because you have  only one
****life and one chance to do all the things you
****want to do.
'''

  

7.截断长文本

1
2
3
4
5
6
7
8
9
10
11
12
13
import textwrap
 
 
text = '''
    There are moments in life when you miss someone
    so much that you just want to pick them
    from your dreams and hug them for real! Dream what
    you want to dream;go where you want to go;
    be what you want to be,because you have
    only one life and one chance to do all the things you want to do.
'''
dedent_text = textwrap.dedent(text).strip()
print(textwrap.shorten(dedent_text, width=50))  # There are moments in life when you miss [...]

  

(三)re:正则表达式

 1.查找文本中的模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import re
 
 
'''
正则表达式是一种用形式化语法描述的文本匹配模式,可以进行复杂的字符串匹配。
Python中的正则表达式通过re模块提供,功能比Python内置的str更强,但是速度没有str提供的方法快。
因此如果内置的str方法可以解决,那么直接用就可以。如果不好解决,再使用正则。
'''
 
 
'''
re最常见的方法就是搜索文本中的模式。match和search函数接收pattern(模式)和text(文本)作为输入,找到这个模式时,就返回一个Match对象。
如果没有找到模式,则返回None。
每个Match对象包含有关匹配性质的信息,包括原输入字符串,所使用的正则表达式,以及模式在原字符串中出现的位置。
'''
pattern = "this"
text = "你知道this的含义吗?"
match = re.search(pattern, text)
print(match)  # <re.Match object; span=(3, 7), match='this'>
print(match.start())  # 3
print(match.end())  # 7
print(text[match.start(): match.end()])  # this
 
 
# 以上是search,那么match呢?
match = re.match(pattern, text)
print(match)  # None
# 返回结果为None,match和search比较类似,但match只能从头匹配查找, 而search可以在字符串的任意位置匹配查找。
 
 
# 即使有多个满足条件的也只会返回第一个。
print(re.search("this""123this456this789this"))  # <re.Match object; span=(3, 7), match='this'>
 
 
# 如果没有则返回None,此时调用下面的start或end方法也会报错。会抛出AttributeError: 'NoneType' object has no attribute 'xxxxxx'
# 因此可以加上一层判断
match = re.match(pattern, text)
if match:
    print(match.start())

  

2.编译表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import re
 
 
'''
我们也可以将模式进行一个编译,比如我们有很多手机号,但我只查询以135开头的。
按照我们刚才讲的,可以这么做。
for number in number_list:
    if re.match("135", number):
        print(number)
这么做毫无疑问是可行的,但是我们每循环一次,pattern都要编译一次。既然pattern是固定不变的,我们是不是可以提前将它编译好呢?
答案是可以的
'''
# 得到编译之后的对象
comp = re.compile("135")
# 下面就可以直接使用这个编译的对象进行查找就可以了
number_list = ["13541258742""18845214415""13512441552"]
for number in number_list:
    # 可以看到,如果使用re的话,需要传入pattern,但是我们将pattern进行编译之后,就可以直接调用了。
    if comp.match(number):
        print(number)
        '''
        13541258742
        13512441552
        '''
 
'''
re.match(pattern, text) <==> re.compile(pattern).match(text)
'''
# 可以对比字符串,准确的说应该对比成类与对象
= "abc|abcd|efg"
print(s.split("|"))  # ['abc', 'abcd', 'efg']
# 也可以使用str这个类来调用,但是不方便,于是我们都使用字符串本身,也就是类str的实例对象
print(str.split(s, "|"))  # ['abc', 'abcd', 'efg']
 
# summary:可以传入pattern和text作为参数,调用re.match,也可以将pattern编译之后,用编译之后的对象调用match方法,此时只需传入text

  

3.多重匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import re
 
 
'''
到目前为止,我们所获取的都是单个实例。如何将满足条件的所有文本全部获取呢?
可以使用findall函数
'''
pattern = "abc"
text = "abc|abc|abc|abc"
print(re.findall(pattern, text))  # ['abc', 'abc', 'abc', 'abc']
# 可以看到,直接将满足条件的所有实例全部以列表的形式获取出来了,并且如果满足条件的实例只有一个,那么得到的仍是一个列表,只是列表里面只有一个元素
 
# 除此之外还有一个迭代器模式
res = re.finditer(pattern, text)
print(res)  # <callable_iterator object at 0x00000000029C4400>
for in res:
    print(v)
    '''
    <re.Match object; span=(0, 3), match='abc'>
    <re.Match object; span=(4, 7), match='abc'>
    <re.Match object; span=(8, 11), match='abc'>
    <re.Match object; span=(12, 15), match='abc'>
    '''
    # 得到的是一个Match对象

  

4.模式语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import re
 
 
'''
除了简单的字面量文本字符串,正则表达式还支持更强大的模式。
'''
# 重复
'''
.:代表除换行符之外的任意字符
*:重复0次或者任意多次
+:重复1次或者任意多次
?:重复0次或者1次
{m}:出现m次
{m,n}:出现m次到n次,包含m和n,并且注意,逗号两边不要有空格
{m,}:省略n,那么表示至少出现m次,最多则没有限制
 
因此:* == {0,}   + == {1,}    ? == {0,1}
'''
 
# b出现一次到三次,注意这里不是ab出现一到三次,*+?{}只会作用于它的前一个字符
print(re.search("ab{1,3}""abcabbc"))  # <re.Match object; span=(0, 2), match='ab'>
print(re.search("ab{2,3}""abcabbc"))  # <re.Match object; span=(3, 6), match='abb'>
'''
可以看到都是从头开始找,开头是ab满足"ab{1,3}",即使后面有abb也不会匹配了
而"ab{2,3}"则要求b至少出现两次,因此找到了后面的abb
'''
 
# 可以看到,要求是一到三次,ab,abb,abbb都符合,最终是取了abbb。
# 正则的模式则是贪婪模式,能往多了匹配就往多了匹配
print(re.search("ab{1,3}""abbbbb"))  # <re.Match object; span=(0, 4), match='abbb'>
 
# *+?这些元字符也是同样的道理
print(re.search("ab*""abbbbb"))  # <re.Match object; span=(0, 6), match='abbbbb'>
print(re.search("ab+""abbbbb"))  # <re.Match object; span=(0, 6), match='abbbbb'>
print(re.search("ab?""abbbbb"))  # <re.Match object; span=(0, 2), match='ab'>
 
 
# 那么如何不使用这种贪婪模式呢?可以直接在*+?{}后面加上?即可,表示关闭贪婪模式
# 出现一到三次,关闭贪婪,只获取一个b
print(re.search("ab{1,3}?""abbbbb"))  # <re.Match object; span=(0, 2), match='ab'>
# *出现0次或多次,关闭贪婪模式,获取0次
print(re.search("ab*?""abbbbb"))  # <re.Match object; span=(0, 1), match='a'>
# 显然获取一次
print(re.search("ab+?""abbbbb"))  # <re.Match object; span=(0, 2), match='ab'>
# 出现0次或一次,关闭贪婪模式后显然获取0次
print(re.search("ab??""abbbbb"))  # <re.Match object; span=(0, 1), match='a'>
# .表示除了换行符的任意字符
print(re.search(".{1,5}""aaaaa"))  # <re.Match object; span=(0, 5), match='aaaaa'>
print(re.search(".{1,5}""aa aa"))  # <re.Match object; span=(0, 2), match='aa'>
 
 
# 字符集
'''
下面问题又来了,如果我想获取以a开头以c结尾,中间的一个字符必须是love中的一个,想获取这样的字符该怎么办呢?
首先两端好解决,关键是中间的字符要怎么表示?
这个时候字符集就派上用场呢?
[love]:用中括号把多个字符组合起来即可,表示匹配l、o、v、e四个字符中的一个
'''
print(re.findall("a[love]c""awc|aec|afc|akc|adc"))  # ['aec']
# 字符集[love]就可以看做是一个整体,也可以搭配元字符使用
'''
[love]{1,3} 表示匹配出现一到三次的字符,什么的字符呢?l、o、v、e四个字符当中的某一个字符
'''
# v在[love]当中,匹配vvv
print(re.findall("[love]{1,3}""vvv"))  # ['vvv']
# k不在但是v在,匹配vv
print(re.findall("[love]{1,3}""kvv"))  # ['vv']
# 匹配v
print(re.findall("[love]{1,3}""kv"))  # ['v']
# a不在,但ve在,匹配ve
print(re.findall("[love]{1,3}""ave"))  # ['ve']
# findall是查找所有,l在[love]里面,o在,v在,但是最多出现三次匹配成功结束。继续查找,最后的e也在,但是_不在,所以匹配结束,得到e。
# y不在,但是o在,u又不在,所以又匹配出e。因此最终结果是['lov', 'e', 'o']。
print(re.findall("[love]{1,3}""love_you"))  # ['lov', 'e', 'o']
 
# 那我如果想取反呢?也就是我要不在love里面的字符。可以使用[^love],表示非l、o、v、e的任意字符
 
# 这便是字符集,但是又有一个问题。如果我想获取为小写字母的字符呢?难道把26的字母都写一遍吗?当然不用
# [a-z]表示所有的小写字母
# [A-Z]表示所有的大写字母
# [0-9]表示所有的数字
# [a-zA-Z0-9]表示所有的字母和数字
print(re.findall("[a-z][A-Z]""aB|ac"))  # ['aB']
print(re.findall("[a-zA-Z]""a|C|4|尻"))  # ['a', 'C']
print(re.findall("[a-zA-Z0-9]""a|C|4|尻"))  # ['a', 'C', '4']
 
# 转义码
# d表示数字
# D表示非数字
# w表示字母、数字,注意:这里包括中文
# W表示非字母数字
# s表示空白符(制表符、空格、换行等)
# S表示非空白符
 
# 但是在操作系统中会先做一层转义,因此需要两个。在操作系统层面上,两个变成一个,然后再和d组合成d进行正则比配
print(re.search("\d{1,3}""1234"))  # <re.Match object; span=(0, 3), match='123'>
# 那如果我想匹配呢?
print(re.search("\\hello""\hello"))  # <re.Match object; span=(0, 6), match='\hello'>
'''
要知道在正则中也是有含义的,因此要匹配纯反斜杠,也需要做一次转义。
所以需要四个,首先在在操作系统层面会转义,将四个变成两个,在正则中两个变成一个,此时的便是普通的字符
但是这样会很麻烦,因此可以在字符串的开头加上一个r,表示这是原生的字符串,你不需要给我做任何的转义
'''
# 此时只需要一个反斜杠即可,可以看到Python中的w是包含中文的
print(re.search(r"w{1,2}""古明地觉"))  # <re.Match object; span=(0, 2), match='古明'>
 
 
# 锚定
'''
有时除了匹配模式的内容外,我们还要对内容的位置进行一个限制。
^:这个符号我们刚才好像见过,[^seq],表示不在seq里面的字符,但是如果放在开头的位置,则表示以某字符开头,如^abc则表示以abc开头的字符
$:表示以某字符结尾。如abc$表示以abc结尾的字符
:单词开头或末尾的空串
B:不在单词开头或末尾的空串
'''
print(re.search(r"^abc""abcd"))  # <re.Match object; span=(0, 3), match='abc'>
print(re.search(r"^abc""aabcd"))  # None
 
print(re.search(r"abc$""dabc"))  # <re.Match object; span=(1, 4), match='abc'>
print(re.search(r"abc$""abcd"))  # None

  

5.限制搜索

1
2
3
4
5
6
7
8
9
10
import re
 
 
'''
已经编译的正则表达式,还可以指定查找的范围
'''
pattern = re.compile(r"123")
text = "123|123|123"
print(pattern.search(text, pos=2, endpos=7))  # <re.Match object; span=(4, 7), match='123'>
# 指定从2开始,7结束

  

6.用组解析匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import re
 
 
'''
有时我们想要提取某些内容中间的内容,比如xxxabcxxx,我想要夹在xxx和xxx之间的内容怎么办呢?
这个时候就可以用到分组
'''
# 有两个abc,但我要的是夹在xxx和xxx之间的abc
match = re.search(r"xxx(abc)xxx""abcxxxabcxxx")
print(match)  # <re.Match object; span=(3, 12), match='xxxabcxxx'>
# 这样匹配依旧会匹配全局,怎么样才能把括号里面的内容给抽取出来呢?
print(match.group(0))  # xxxabcxxx
print(match.group(1))  # abc
'''
可以使用group函数,参数默认是0,表示全局。
参数为1,表示第一个括号里面的内容,参数为n,表示第n个括号里面的内容
'''
 
match = re.search(r"123(.+?)456(.+?)789""123abc456def789")
print(match.group(0))  # 123abc456def789
print(match.group(1))  # abc
print(match.group(2))  # def
 
# 那么问题来了,如果是这样呢?
match = re.search(r"123((.+?)456(.+?))789""123abc456def789")
print(match.group(0))  # 123abc456def789
print(match.group(1))  # abc456def
print(match.group(2))  # abc
print(match.group(3))  # def
'''
可以看到,我在(.+?)456(.+?)外层又加上了一层括号
分组的顺序,是从左往右,找到了(,就会进行分组,至于),会自动进行匹配,所以group(1)是最外层的abc456def
group(2)是456左边的(.+?),group(3)是456右边的(.+?)
'''
 
# 此外还可以给组进行命名。只需要在括号里面加上?P<name>即可,name是我们指定的分组的名字
match = re.search(r"123(?P<yoyoyo>.+?)456(?P<哈哈哈>.+?)789""123纳尼456我屮艸芔茻789")
print(match.group("yoyoyo"), match.group("哈哈哈"))  # 纳尼 我屮艸芔茻
print(match.group(1), match.group(2))  # 纳尼 我屮艸芔茻
# 并且使用组的序号依旧是可以的
 
 
# 除了group之外,还可以使用groupdict和groups
match = re.search(r"123(?P<yoyoyo>.+?)456(?P<哈哈哈>.+?)789""123纳尼456我屮艸芔茻789")
print(match.groups())  # ('纳尼', '我屮艸芔茻')
print(match.groupdict())  # {'yoyoyo': '纳尼', '哈哈哈': '我屮艸芔茻'}
 
# groups无论是否指定组名,都会获取到。但是groupdict只会获取指定组名的分组,比如说:
match = re.search(r"123(?P<yoyoyo>.+?)456(.+?)789""123纳尼456我屮艸芔茻789")
print(match.groups())  # ('纳尼', '我屮艸芔茻')
# 可以看到没有指定组名的就获取不到了
print(match.groupdict())  # {'yoyoyo': '纳尼'}
 
 
# 此外在分组的时候,还可以指定管道符|,表示或者。比如
# jpg|png表示获取的是jpg或者png,注意:管道符会作用于两边的全部字符,比如:
# www.mashiro.jpg|png,则表示的是www.mashiro.jpg或者png
# www.mashiro.(jpg|png),此时的管道符只会作用于括号里面两端的字符,表示www.mashiro.jpg或者www.mashiro.png
print(re.search(r"www.mashiro.(jpg|png)""www.mashiro.jpg"))  # <re.Match object; span=(0, 15), match='www.mashiro.jpg'>
# 注意这里的.表示转义,让.表示普通的. 不是具有匹配字符能力的.
 
# 但是这里又出现问题了,比如说
text = '''
    这里有图片哦www.1.jpg,有很多格式的哦
    www.2.png,想看吗,还会动哦,www.3.gif
    那还等什么呢?www.banana.jpg,
    快去吧,那个象征自由的男人在等着你www.象征自由的男人--尻比.png
'''
res = re.findall(r"www.+?(jpg|png|gif)", text)
print(res)  # ['jpg', 'png', 'gif', 'jpg', 'png']
'''
可以看到findall获取的自动是分组里面的内容,可我们要的是整体的链接啊。怎么办呢?一种方法是我在外面也加上括号不就行了
'''
 
res = re.findall(r"(www.+?(jpg|png|gif))", text)
print(res)  # [('www.1.jpg', 'jpg'), ('www.2.png', 'png'), ('www.3.gif', 'gif'), ('www.banana.jpg', 'jpg'), ('www.象征自由的男人--尻比.png', 'png')]
# 可以看到最外层的分组也被我们所捕获了,多个分组的内容回族和成一个元组。因此可以使用索引获取链接,但这还不是最完美的方法。
# 有什么办法,让里面的那个分组失去效果呢?就是说,我给你加上括号只是为了多匹配一些格式罢了,你就不要自作聪明地当做分组来处理了
# 答案是有的,只需要加上?:即可,在括号里面加上?:表示让分组失去效果,也就是不表示分组
res = re.findall(r"www.+?(?:jpg|png|gif)", text)
print(res)  # ['www.1.jpg', 'www.2.png', 'www.3.gif', 'www.banana.jpg', 'www.象征自由的男人--尻比.png']
# 可以看到匹配成功,而且此时最外层也不需要再加上括号了,因为里面的分组失去效果了,相当于就没有分组了,如果没有分组,那么默认匹配整体。
 
 
# 最后插一句,聊一聊www.+?(?:jpg|png|gif),为什么要是.+?呢,如果是.+行不行,比如:
print(re.search(r"www.+?jpg""www.1.jpg我屮艸芔茻www.2.jpg").group())  # www.1.jpg
# 我把?去掉,那么等于变成了贪婪模式。
print(re.search(r"www.+jpg""www.1.jpg我屮艸芔茻www.2.jpg").group())  # www.1.jpg我屮艸芔茻www.2.jpg
# 那么会从第一个www开始,匹配到最后一个jpg,因此要注意开启非贪婪模式

  

7.搜索选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import re
 
 
'''
选项标志是用来改变匹配引擎处理表达式的方式。
比如说,我们可以看看re.search()的函数原型
def search(pattern, string, flags=0):
这个pattern和string我们都知道,但是最后一个参数flags是什么鬼?
这个flags就是选项标志,也是我们接下来要说的内容
'''
 
# 大小写无关
'''
有些时候,我们不想区分大小写该怎么办呢?比如说aa和AA我们认为都是合法的。
'''
# 这便是flags的作用,可以用来改变引擎处理表达式的方式
# re.IGNORECASE表示忽略大小写敏感模式,其中re.IGNORECASE也可以写成re.I
match = re.match(r"aa""aA", flags=re.IGNORECASE)
print(match)  # <re.Match object; span=(0, 2), match='aA'>
 
 
# 多行输入
'''
有两个标志会影响如何完成多行输入的搜索:MULTILINE和DOTALL。
MULTILINE表示^和*的锚定规则除了应用于整个字符串之外,还对每一行的开头和结尾起作用。(不常用)
DOTALL表示.这个元字符可以匹配换行符,默认情况下,.这个元字符是不能匹配换行符的,但是加上这个参数之后,便可以匹配换行符
 
注:re.MULTILINE可以写成re.M,re.DOTALL可以写成re.S
'''
match = re.match(r".+""aabb cc")
print(match)  # <re.Match object; span=(0, 4), match='aabb'>
match = re.match(r".+""aabb cc", flags=re.DOTALL)
print(match)  # <re.Match object; span=(0, 7), match='aabb cc'>
 
 
# Unicode
'''
默认情况下,正则表达式处理的都是Unicode文本,也就是说w是可以匹配中文的。
如果我想只匹配ASCII码呢?可以使用re.ASCII,这样的话就不会匹配中文了.
re.ASCII也可以写成re.A
'''
print(re.match(r"w+""love中国"))  # <re.Match object; span=(0, 6), match='love中国'>
print(re.match(r"w+""love中国", flags=re.ASCII))  # <re.Match object; span=(0, 4), match='love'>

  

8.字符串替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re
 
 
'''
我们也可以将字符串进行一个替换
'''
text = "when i was young, i'd listen to the radio"
# 我要把当中所有的i替换成大写的I,怎么做呢?
# 可以使用re.sub函数,def sub(pattern, repl, string, count=0, flags=0):
# 参数:要替换的字符模式  替换成哪些字符  文本  替换的数量(默认为0,表示全部)  匹配模式
print(re.sub(r"i""I", text))  # when I was young, I'd lIsten to the radIo
 
# 我要把里面的英文全部删除
text = "古明地觉(komeiji satori)是一个来自于东方地灵殿的女孩,它有一个妹妹,叫古明地恋(komeiji koishi)"
# 把w都替换成空即可,但是注意w默认是匹配中文(以及日文等等)的,所以要加上flags=re.A,表示只匹配ASCII码中的字符
print(re.sub(r"w", "", text, flags=re.A))  # 古明地觉( )是一个来自于东方地灵殿的女孩,它有一个妹妹,叫古明地恋( )
# 不加flags的话,会把中文也剥掉了
print(re.sub(r"w", "", text))  # ( ),,( )
 
# 除此之外还有一个函数叫做subn,和sub一样,但是除了返回替换的内容,还会返回替换的数量
print(re.subn("a""b""accaccacc"))  # ('bccbccbcc', 3)

  

9.利用模式拆分

1
2
3
4
5
6
7
8
9
10
11
12
import re
 
'''
类似于str.split,re也可以使用split函数,而且功能更加强大
'''
text = "abc1def55455ghi6621"
# 我要按照数字切割,最终只保留,abc def ghi该怎么做呢?
# 直接调用split即可, 此外还可以指定最多分割多少次,不指定默认为全部。这里表示用数字切割
print(re.split(r"d", text))  # ['abc', 'def', '', '', '', '', 'ghi', '', '', '', '']
# 可以手动去掉空格
no_space = list(filter(lambda x: len(x), re.split(r"d", text)))
print(no_space)  # ['abc', 'def', 'ghi']

  

(四)difflib:比较序列

1
2
3
4
5
6
7
import difflib
 
 
'''
difflib模块包含一些计算和处理序列之间差异的工具。
它对于比较文本及其有用,其中的函数可以使用多种常用的差异格式生成报告
'''

  

1.比较文本体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import difflib
 
 
text1 = '''Half the people on our streets look as though life was a sorry business.
It is hard to find a happy looking man or woman. xxx
Worry is the cause of their woebegone appearance.
Worry makes the wrinkles; worry cuts the deep, down-glancing lines on the face;
worry is the worst disease of our modern times.
'''
text2 = '''Half the people on our streets look as though life was a sorry business.
It is hard to find a happy looking man or woman.
Worry is the cause of their woebegone appearance.
Worry makes the wrinkles; worry cuts the deep, down-glancing lines on the face;
worry is the worst disease of our modern timeS.
xxx xxx  xxx  xxx
'''
# 将文本分解成由单个文本行构成的序列,与传入大量字符串相比,会有更可读的输出
text1_lines = text1.splitlines()
text2_lines = text2.splitlines()
 
 
= difflib.Differ()
diff = d.compare(text1_lines, text2_lines)
'''
differ类用于处理文本行序列,并生成可读的差异(deltas)或更改指令,包括各行中的差异。
differ生成的默认输出和Unix下的diff命令行工具类似,包括两个列表的原始输入值(包含共同的值),以及指示做了哪些更改的标记数据。
    *有-前缀的行在第一个序列中,而非第二个序列。
    *有+前缀的行在第二个序列中,而非第一个序列
    *如果某一行在不同版本之间存在增量差异,那么也会使用一个加?的前缀的额外行来强调新版本中的变更
    *如果一行未改变,则会打印输出,而且左列有一个额外的空格,使它与其他可能有差异的输出对齐
'''
# 得到的diff是一个生成器
print(" ".join(diff))
# 输出结果如下
'''
  Half the people on our streets look as though life was a sorry business.
- It is hard to find a happy looking man or woman. xxx
?                                                  ---
 
+ It is hard to find a happy looking man or woman.
  Worry is the cause of their woebegone appearance.
  Worry makes the wrinkles; worry cuts the deep, down-glancing lines on the face;
- worry is the worst disease of our modern times.
?                                              ^
 
+ worry is the worst disease of our modern timeS.
?                                              ^
 
+ xxx xxx  xxx  xxx
'''
# 首先第一行是一样的,直接打印
# 第二行,第一个序列,我故意在结尾多添了xxx,所以输出已经用___进行了标记,并且开头出现了?表示强调出现了变更,说白了我觉得就是为了做标记而单起一行
# 如果出现了差异,那么两个序列都会打印出现差异的行,因此下面还会输出一次,并且前缀是+,表示这是第二个序列
# 然后面下面两个是没有问题的,所以正常输出
# 然后再下一行我故意将times替换成了timeS,因此出现差异。
# 最后一行我多添加了xxx xxx  xxx  xxx,也显示了第二个序列多了这么些内容

  

2.寻找最长的相同子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from difflib import SequenceMatcher
 
 
text1 = "abcd"
text2 = "bcde"
s1 = SequenceMatcher(None, text1, text2)
# 寻找最长匹配的字符,里面接收四个参数,分别为text1查找的起始位置和终止位置,text2查找的起始位置和终止位置
match = s1.find_longest_match(0len(text1), 0len(text2))
print(match)  # Match(a=1, b=0, size=3)
print(match.a, match.b, match.size)  # 1 0 3
'''
a:第一个序列开始的索引位置
b:第二个序列开始的索引位置
size:序列的长度
'''
print(text1[match.a: match.a+match.size])  # bcd
print(text2[match.b: match.b+match.size])  # bcd
 
 
text1 = "when i was young, i would listen to the radio"
text2 = "when i was old, i will listen to the radio"
s2 = SequenceMatcher(None, text1, text2)
match = s2.find_longest_match(0len(text1), 0len(text2))
print(match)  # Match(a=25, b=22, size=20)
 
 
text1 = "abcdefkaaa"
text2 = "abcdefkaaa"
# 这里的第一个参数可以指定成一个匿名函数,这里表示当遇到k这个字符就停止扫描(包括k)
# 如果不指定那么由于两个序列一样,会得到全部
s2 = SequenceMatcher(lambda x: x == "k", text1, text2)
match = s2.find_longest_match(0len(text1), 0len(text2))
print(match)  # Match(a=0, b=0, size=7)
 
 
# 此外还可以计算两个字符的相似程度
text1 = "我的梦想是开飞机"
text2 = "我的梦想是开拖拉机"
s3 = SequenceMatcher(None, text1, text2)
print("%.2f" % s3.ratio())  # 0.82
 
 
# 有点类似于一个库fuzzywuzzy
from fuzzywuzzy import fuzz
= fuzz.ratio("我的梦想是开飞机""我的梦想是开拖拉机")
print(p)  # 82
p2 = fuzz.ratio("我有一只猫""我有一只猫咪!!!")
print(p2)  # 71
p3 = fuzz.partial_ratio("我有一只猫""我有一只猫咪!!!")
# 可以看到partial_ratio是如果一方结束了,就不在匹配了,所以这里是100
print(p3)  # 100
原文地址:https://www.cnblogs.com/valorchang/p/11395809.html