1.正则表达式

Regular Expression,正则表达式

用来描述匹配规则的表达式,针对字符串进行比较和匹配

作用:用来检索、替换哪些符合某个规则的文本

使用场景:

  • 验证数据 前端,电话/邮箱
  • 抓数据 页面抓取数据
  • 洗数据 洗掉冗余数据

1.1Python 里的正则表达式(re 模块)

  1. match(匹配规则,被匹配的字符串)方法==从头==开始匹配,匹配成功返回 match 对象,匹配失败返回 None(只匹配一次)
  2. 从 match 对象获取字符串的方法为.group()
  3. 获取索引位置的元组span()、起始位置start()、结束位置end()
import re

s = "我喜欢吃火锅"
ret = re.match("我喜欢",s)
print(ret)

print(ret.group())
print(ret.span())
print(ret.start())
print(ret.end())

out:
<_sre.SRE_Match object; span=(0, 3), match='我喜欢'>
我喜欢
(0, 3)
0
3
  1. search(匹配规则,被匹配的字符串)从字符串中进行搜索,一旦匹配成功返回第一次匹配成功的位置,不成功返回 None,使用方法和 match 一样(只匹配一次)
import re

s = "我喜欢吃火锅我喜欢吃火锅"

ret1 = re.search("火锅",s)
print(ret1)
print(ret1.group())

out:
<_sre.SRE_Match object; span=(4, 6), match='火锅'>
火锅
  1. findall(匹配规则,被匹配的字符串),搜索全部,匹配全部,返回一个列表,包含所有符合规则的字符串。

  2. finditer(匹配规则,被匹配的字符串),搜索全部,匹配全部,返回一个迭代器,对象是 match 对象。

import re

s = "我喜欢吃火锅我喜欢吃火锅"

ret1 = re.findall("火锅", s)
print(ret1)
ret2 = re.finditer("火锅", s)
for i in ret2:
    print(i)


out:
['火锅', '火锅']
<_sre.SRE_Match object; span=(4, 6), match='火锅'>
<_sre.SRE_Match object; span=(10, 12), match='火锅'>
  1. split()将字符串按照匹配规则进行切割,返回一个切割后的列表
import re

s = "asdf jkl;     asdf  werq,asdf,   foo"

# 先匹配一个空格,后面可能有任意个空格
ret = re.split("[\s,;]\s*", s)
print(ret)

out:
['asdf', 'jkl', 'asdf', 'werq', 'asdf', 'foo']
  1. sbu(规则,替换的串,被匹配的字符串)用来做高级替换
import re

s = "我还要再活500年,20年"

ret = re.sub("\d+", "1000", s)
print(ret)
ret = re.sub("\d+", "2000", s)
print(ret)

out:
我还要再活1000年,1000年
我还要再活2000年,2000年

sub()还有高阶用法,传入函数,对匹配到的 Match 对象进行操作,并返回一个字符串

import re

s = "我还要再活500年,20年"


def func(num):
    # 匹配到的是一个Match对象,所以传进来是个Match对象,要使用group获取字符串
    n = num.group()
    return str(int(n) + 1)


ret = re.sub("\d+", func, s)
print(ret)
ret = re.sub("\d+", func, s)
print(ret)

out:
我还要再活501年,21年
我还要再活501年,21年

1.2 正则表达式的语法

1.2.1 单字符匹配

方法含义
.匹配单字符(万能匹配,什么都可以匹配),但是不能匹配\n
\d匹配所有的数字(0-9 的单字符)
\D匹配所有的非数字(0-9 的单字符)
\w匹配所有的单词字符(数字、字母和下划线),Python3 中汉字也是单词字符
\W上述相反
\s匹配所有空白符
\S匹配所有非空白符
[]内部任意一个(^表示非)

案例:

ret = re.match("张.龙", "张海龙")
print(ret)
ret = re.match("张.龙", "张龙")
print(ret)
ret = re.match("张.龙", "张\n龙")
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='张海龙'>
None
None
ret = re.match("张.龙", "张2龙")
print(ret)
ret = re.match("张\d龙", "张2龙")
print(ret)
ret = re.match("张\d龙", "张12龙")
print(ret)
ret = re.match("张\d龙", "张海龙")
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='张2龙'>
<_sre.SRE_Match object; span=(0, 3), match='张2龙'>
None
None
ret = re.match("张\D龙", "张2龙")
print(ret)
ret = re.match("张\D龙", "张s龙")
print(ret)
ret = re.match("张\D龙", "张#龙")
print(ret)
ret = re.match("张\D龙", "张海龙")
print(ret)

out:
None
<_sre.SRE_Match object; span=(0, 3), match='张s龙'>
<_sre.SRE_Match object; span=(0, 3), match='张#龙'>
<_sre.SRE_Match object; span=(0, 3), match='张海龙'>
ret = re.match("张\w龙", "张海龙")
print(ret)
ret = re.match("张\w龙", "张s龙")
print(ret)
ret = re.match("张\w龙", "张#龙")
print(ret)
ret = re.match("张\w龙", "张2龙")
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='张海龙'>
<_sre.SRE_Match object; span=(0, 3), match='张s龙'>
None
<_sre.SRE_Match object; span=(0, 3), match='张2龙'>
ret = re.match("张\W龙", "张海龙")
print(ret)
ret = re.match("张\W龙", "张s龙")
print(ret)
ret = re.match("张\W龙", "张#龙")
print(ret)
ret = re.match("张\W龙", "张2龙")
print(ret)

out:
None
None
<_sre.SRE_Match object; span=(0, 3), match='张#龙'>
None
ret = re.match("张\s龙", "张 龙")
print(ret)
ret = re.match("张\s龙", "张\n龙")
print(ret)
ret = re.match("张\s龙", "张\r龙")
print(ret)
ret = re.match("张\s龙", "张\t龙")
print(ret)
ret = re.match("张\s龙", "张\f龙")
print(ret)
ret = re.match("张\s龙", "张海龙")
print(ret)
ret = re.match("张\s龙", "张龙")
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='张 龙'>
<_sre.SRE_Match object; span=(0, 3), match='张\n龙'>
<_sre.SRE_Match object; span=(0, 3), match='张\r龙'>
<_sre.SRE_Match object; span=(0, 3), match='张\t龙'>
<_sre.SRE_Match object; span=(0, 3), match='张\x0c龙'>
None
None
ret = re.match("张\S龙", "张 龙")
print(ret)
ret = re.match("张\S龙", "张\n龙")
print(ret)
ret = re.match("张\S龙", "张\r龙")
print(ret)
ret = re.match("张\S龙", "张\t龙")
print(ret)
ret = re.match("张\S龙", "张\f龙")
print(ret)
ret = re.match("张\S龙", "张海龙")
print(ret)
ret = re.match("张\S龙", "张龙")
print(ret)

out:
None
None
None
None
None
<_sre.SRE_Match object; span=(0, 3), match='张海龙'>
None
import re

ret = re.match("张[\d\w\s#]龙", "张#龙")
print(ret)
ret = re.match("张[a-zA-Z0-9]龙", "张1龙")
print(ret)
# ^表示非
ret = re.match("张[^a-zA-Z0-9]龙", "张1龙")
print(ret)
ret = re.match("张[^a-zA-Z0-9]龙", "张海龙")
print(ret)
ret = re.match("张[^a-zA-Z0-9]龙", "张A龙")
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='张#龙'>
<_sre.SRE_Match object; span=(0, 3), match='张1龙'>
None
<_sre.SRE_Match object; span=(0, 3), match='张海龙'>
None

1.2.2 多字符匹配

方法含义
*匹配 0 或任意个前面的字符
+匹配 1 或任意个前面的字符
匹配 0 或 1 个前面的字符
{m}匹配指定 m 个前面的字符
数量不足返回失败
{n,m}匹配范围[n-m]数量前面的字符
数量不足返回失败
import re

s = "我的电话是12345678905."

# 匹配成功,但是是空,因为*是匹配0或任意个,匹配到了0个
ret = re.search("\d*", s)
print(ret)

# 匹配成功,因为+是匹配1或任意个
ret = re.search("\d+", s)
print(ret)

ret = re.search("\d?", s)
print(ret)

s = "0451-88888888"
ret = re.search("0\d+-\d{8}", s)
print(ret)

out:
<_sre.SRE_Match object; span=(0, 0), match=''>
<_sre.SRE_Match object; span=(5, 16), match='12345678905'>
<_sre.SRE_Match object; span=(0, 0), match=''>
<_sre.SRE_Match object; span=(0, 13), match='0451-88888888'>

1.2.3 边界匹配

r 开头引号的字符串表示正则表达式,不然有些会有转义效果

方法含义
^必须以规则开头匹配(电话)
$必须以规则结尾匹配(邮箱)
\b位置两侧不可以都是单词字符
\B位置两侧必须都是单词字符
\A匹配字符串开始的位置
\Z匹配字符串结束的位置
s = "asd"

ret = re.search(r"\ba\w+", s)
print(ret)

s = "sasd"
ret = re.search(r"\ba\w+", s)
print(ret)

out:
<_sre.SRE_Match object; span=(0, 3), match='asd'>
None

1.2.4 分组匹配

|符号匹配两边任意一边的正则表达式即可,

pattern参数写一个串

分组:

分组内部进行或匹配,外部相同。

group(0)是所有

例子:

import re

s = "我喜欢吃火锅,我喜欢吃小面,我喜欢吃拉面,我喜欢吃乌冬面,我喜欢吃烧鸡公,"

ret = re.search("我喜欢吃(火锅|小面)", s)
print(ret)
print(ret.group())
print(ret.group(0))
print(ret.group(1))

ret = re.search("(我喜欢吃)(火锅|小面)", s)
print(ret)
print(ret.group())
print(ret.group(0))
print(ret.group(1))
print(ret.group(2))


out:
<_sre.SRE_Match object; span=(0, 6), match='我喜欢吃火锅'>
我喜欢吃火锅
我喜欢吃火锅
火锅
<_sre.SRE_Match object; span=(0, 6), match='我喜欢吃火锅'>
我喜欢吃火锅
我喜欢吃火锅
我喜欢吃
火锅

复用分组匹配的内容:

s = "<h1>我是标题标签</h2>"

ret = re.match(r"<\w+>.*</\w+>", s)
print(ret)

ret = re.match(r"<(\w+)>.*</\1>", s)
print(ret)

s = "<h1>我是标题标签</h1>"
ret = re.match(r"<(\w+)>.*</\1>", s)
print(ret)

s = "<body><h1>我是标题标签</h1></body>"
ret = re.match(r"<(\w+)><(\w+)>.*</\2></\1>", s)
print(ret)

out:
<_sre.SRE_Match object; span=(0, 15), match='<h1>我是标题标签</h2>'>
None
<_sre.SRE_Match object; span=(0, 15), match='<h1>我是标题标签</h1>'>
<_sre.SRE_Match object; span=(0, 28), match='<body><h1>我是标题标签</h1></body>'>

分组命名:

定义:(?P<name>正则),P 是大写

使用:(?P=name)

例子:

s = "<body><h1>我是标题标签</h1></body>"
ret = re.match(r"<(?P<body>\w+)><(?P<h1>\w+)>.*</(?P=h1)></(?P=body)>", s)
print(ret)

out:
<_sre.SRE_Match object; span=(0, 28), match='<body><h1>我是标题标签</h1></body>'>

1.3 一些其他参数(Flag)

参数含义
re.IGNORECASE(re.I)忽略大小写
re.MULTILINE(re.M)可换行(\n自动进入下一行)
re.ASCII(re.A)使\w/\W/d/\D/\s/\S/\b/\B 只匹配 ASCII 字符,不匹配 Unicode 字符
re.Unicode(re.U)默认使用此标志位,\w/\W/d/\D/\s/\S/\b/\B 会匹配 Unicode 字符,如果指定了 re.A 标志,则 re.U 失效
re.DOTALL(re.S)使得.会匹配换行符等其他字符
re.VERBOSE(re.X)允许整个正则表达式写成多行,忽略空白字符,并可以添加#开头的注释,这样更美观。

1.4 贪婪和非贪婪

正则表达式默认情况下会尽可能多的匹配,

如:

asdf.png///123.png

如果写\w+.png不会匹配到 asdf.png

而是会匹配整个串

如果需要修改为非贪婪,就要在一些情况后面加上?操作符

包括:+,?,*,[n-m]

在使用的时候在这些符号后面加上?即可更改匹配模式为非贪婪

2.可迭代器

2.1 可迭代对象

使用iter()来获取可迭代对象(是可迭代对象就返回对象,如果不是就报错)

可迭代对象就是一个可以通过 iter 方法获取到迭代器(iterator)的对象。

可迭代对象:

  • 对象实现了能返回迭代器的__iter__()方法
  • 对象实现了__getitem__(index)方法,而且 index 参数是从 0 开始的整数(索引)

Python 中内置的序列类型,如list、tuple、str、bytes、dict、set、collections.deque等都可以迭代,因为都实现了__getitem__(index)方法。

注意:其实标准的序列还都实现了 __iter__() 方法

2.1.1 判断是否是一个可迭代对象

from collections import abc

l = [1, 2, 3, 4]

ret = isinstance(l,abc.Iterable)

print(ret)

out:
True

最准确的方法还是调用iter(l)iter会考虑历史遗留的__getitem__(index)方法,abc.Iterable 只会考虑__iter()__方法。

2.1.2 构建可迭代对象

  • __iter__()
  • __getitem__(index)

只要实现其中一个就是可迭代对象

如果实现了__iter__() 方法,但是该方法没有返回迭代器的时候

调用abc.Iterable会返回True的错误判断。

iter()会抛出异常

2.1.3iter()函数

Python 迭代器要求iter()必须返回特殊的迭代器对象。

迭代器对象必须实现__next__()方法,并使用StopIteration异常来通知迭代结束

iter()函数有两种使用方法:

  • iter(iterable) -> iterator:传入可迭代对象,返回迭代器
  • iter(callable,sentinel) -> iterator:传入两个参数,第一个必须是可调用的对象(没有参数),用于不断调用,产出各个值,第二个值是==哨符==,是一个标记值,当第一个参数的调用返回了这个值的时候,触发迭代器抛出StopIteration异常,==不产出哨符==

例子:投骰子

import random


def d6():
    return random.randint(1,6)


d6_iter = iter(d6 , 1)
print(d6_iter)

for it in d6_iter:
    print(it)

out:
<callable_iterator object at 0x000002AC2B708A58>
4
6

例子:逐行读取文件,直到遇到空行或者达到文件末尾为止

with open('mydata.txt') as fp:
    for line in iter(fp.readline, '\n'):
        # fp.readline每次返回一行
        print(line)

2.2 迭代器

内存中放不下数据集的时候,要使用一种惰性的获取数据的方式,即按需要一次获取一个数据对象。

这就是迭代器模式(Iterator pattern)

迭代器就是实现了无参数__next__()方法(无参数),返回序列中的下一个元素。

如果没有元素了,就抛出StopIteration异常。

即迭代器可以被next()函数调用,并不断返回下一个元素的值。

在 Python 语言内部, 迭代器 用于支持:

  • for 循环
  • 构建和扩展集合类型
  • 逐行遍历文本文件
  • 列表推导、字典推导和集合推导
  • 元组拆包
  • 调用函数时,使用*拆包实参

2.2.1 判断对象是否为迭代器

最好是使用isinstance(x,abc.Iterator)

from collections import abc

l = [1,2,3,4]
it = iter(l)

print(isinstance(l, abc.Iterable))
print(isinstance(l, abc.Iterator))
print(iter(l))
print(isinstance(it, abc.Iterable))
print(isinstance(it, abc.Iterator))
print(iter(it))

out:
True
False
<list_iterator object at 0x000001E5516AA2E8>
True
True
<list_iterator object at 0x000001E5515FB278>

Python 中内置的序列类型,如 list、tuple、str、bytes、dict、set、collections.deque 等都是 可迭代的对象,但不是迭代器; 生成器一定是迭代器

2.2.2__next__()__iter__()

class A:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        # 返回一个迭代器
        return self

    def __next__(self):
        # 返回下一个元素
        # 有一个索引在记录每次返回的位置
        try:
            ret = self.data[self.index]
            self.index += 1
        except IndexError:
            raise StopIteration()
        return ret

iter 方法会调用__iter__方法返回当前迭代器对象

next 方法会调用__next__方法返回结果,同时索引增加(index 必须开始是 0 的整数)

2.2.3next()函数获取迭代器中下一个元素

除了可以用 for 循环处理迭代器中的元素外,可以直接使用next()方法

如果是最后一个,则 for 不会报错,next()方法报错,原因是 for 循环是一个上下文管理器,会自动捕获异常,而 next()不会。其他迭代上下文也是如此。(列表推导、元组拆包等)

或者为next()函数指定第二个参数(默认值),执行到末尾后,返回默认值,不会抛出异常

with open('/etc/passwd') as fd:
    while True:
        line = next(fd, None)
        if line is None:
            break
        print(line, end='')

2.2.4 可迭代的对象与迭代器的对比

Python 从可迭代对象中获取迭代器。

for 会先调用iter()方法将字符串转换成迭代器。然后进行迭代。

如果不使用 for 循环,则使用 while 来模拟:

it = iter("123")

while True:
    try:
        print(next(it))
    except StopIteration:
        del it
        break

总结:

  • 迭代器要实现__next__()方法,返回迭代器中的下一个元素

  • 迭代器还要实现__iter__()方法,返回迭代器自身。==迭代器可以迭代,迭代器都是可迭代对象==

  • 可迭代对象一定要实现__iter__()方法,返回一个迭代器,==但是不能返回自身!==

    也不能实现__next__()方法

练习题:

1.截取

import re

s = """<div class="WB_text W_f14" node-type="feed_list_content" nick-name="林俊杰">飛機上偶遇的 J(JJ)F(Fish)J (Jimmy+Jolin)組合!<br><a
        target="_blank" render="ext" extra-data="type=atname"
        href="//weibo.com/n/%E6%A2%81%E9%9D%99%E8%8C%B9?from=feed&loc=at" usercard="name=梁静茹">@梁静茹</a> <a
        target="_blank" render="ext" extra-data="type=atname"
        href="//weibo.com/n/%E8%94%A1%E4%BE%9D%E6%9E%97?from=feed&loc=at" usercard="name=蔡依林">@蔡依林</a> <a
        target="_blank" render="ext" extra-data="type=atname"
        href="//weibo.com/n/%E5%A4%A2%E6%83%B3%E5%AE%B6%E6%9E%97%E5%BF%97%E7%A9%8E?from=feed&loc=at"
        usercard="name=夢想家林志穎">@夢想家林志穎</a><br> 祝大家2018新年快樂!
</div>
"""

ret = re.sub("<br>.*<br>", "  ", s, flags=re.S)
ret = re.findall(r"(?<=>)(.*?)<", ret, flags=re.S)
print(ret)

out:
['飛機上偶遇的 J(JJ)F(Fish)J (Jimmy+Jolin)組合!   祝大家2018新年快樂!\n']

2.邮箱验证

5-20 位单词字符

@

域名

.com

import re

s = "185699189@qq.com"

ret = re.match(r"^(\w{5,20})@(\w+.com)$", s)
print(ret.group(1))
print(ret.group(2))