唐抉的个人博客

python学习笔记(十一)

字数统计: 2.7k阅读时长: 12 min
2022/10/13

正则表达式

正则表达式是一种用来匹配字符串强有力的武器。凡是符合规则的字符串,就认为它“匹配”了,否则该批字符串就是不合法的。正则表达式是用字符串表示,其用字符匹配规则为:

若在正则表达式中直接给出字符,就是精确匹配。

匹配一个数字: \d

'00\d'可以匹配'007'但无法匹配'00A'

'\d\d\d'可以匹配'029'

匹配一个字母或数字: \w

'\w\w\d'可以匹配'as2'

匹配任意一个字符:.

'py.'可以匹配'pya''py@''py2'等。

匹配任意个字符(包括0个): *

匹配至少一个字符: +

匹配零个或一个字符: ?

匹配n个字符: {n}

匹配n-m个字符: {n-m}

匹配一个空格(包括Tab等空白符): \s

匹配至少一个空格(包括Tab等空白符): \s+

特殊字符要用\+特殊字符转义。

进阶

若要做更精确地匹配,可以用[]表示范围。

匹配一个数字、字母或者下划线: [0-9a-zA-Z\_]

注意: [0-9a-zA-Z\_]中的0-9代表数字,a-zA-Z代表字母,\_代表下划线。

匹配至少由一个数字、字母或者下划线组成的字符串: [0-9a-zA-Z\_]+

注意: [0-9a-zA-Z\_]+中的+代表大于等于1次的匹配前面的子表达式。

匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串: [a-zA-Z\_][0-9a-zA-Z\_]*

匹配长度为1-20个字符的变量(前面1个字符+后面最多19个字符): [a-zA-Z\_][0-9a-zA-Z\_]{0,19}

匹配A或B: A|B

表示行的开头: ^

表示必须以数字开头: ^\d

表示行的结束: $

表示必须以数字结束: \d$

re模块

python提供了re模块,包含所有正则表达式的功能,由于python的字符串本身也用\转义,所以在字符串需要转义时,在字符串前面加r前缀。如

1
2
s='ABc\\-001'#python的字符串,其对应的正则表达式字符串变成'ABc\-001'
s=r'ABc\-001'#其对应的正则表达式字符串不变'ABc\-001'

re模块的match()方法判断正则表达式是否匹配,若匹配成功,返回一个match对象,否则返回None

1
2
3
4
5
6
import re
print(re.match(r'^\d{3}\-\d{3,8}$','010-12345'))
print(re.match(r'^\d{3}\-\d{3,8}$','010 12345'))
#运行结果如下:
<re.Match object; span=(0, 9), match='010-12345'>
None

常见的判断方法是:

1
2
3
4
5
test='用户输入的字符串'
if re.match(r'正则表达式',test):
print('ok')
else:
print('failed')

切分字符串

如果用户输入了一组标签,记得用正则表达式把不规范的输转化成正确的数组。

正常的切分代码:

1
2
3
print('a b   c'.split(' '))
#运行结果如下:
['a', 'b', '', '', 'c']

用正则表达式切分字符串:

1
2
3
4
import re
print(re.split(r'\s+','a b c'))
#运行结果如下:
['a', 'b', 'c']

往字符串里增加,:;等符号,正则表达式切分:

1
2
3
4
import re
print(re.split(r'[\s+\,\:\;]+','a b c,d : ; c'))
#运行结果如下:
['a', 'b', 'c', 'd', 'c']

分组

除了简单判断是否匹配外,正则表达式还有提取子串的强大功能。用()表示的是要提取的分组。

比如^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取 出区号和本地号码:

1
2
3
4
5
6
7
8
9
10
11
import re
m=re.match(r'^(\d{3})-(\d{3,8})$','010-12345')
print(m)
print(m.group(0))
print(m.group(1))
print(m.group(2))
#运行结果如下:
<re.Match object; span=(0, 9), match='010-12345'>
010-12345
010
12345

正则表达式可以直接识别合法的时间,但有时用正则表达式也无法做到完全验证。

比如识别日期:

1
2
3
4
5
6
7
8
9
10
11
12
import re
t='19:05:30'
g='2-30'#2-30为非法日期
#识别时间
m=re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$',t)
#识别日期
x=re.match(r'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$',g)
print(m.groups())
print(x.groups())
#运行结果如下:
('19', '05', '30')
('2', '30')

贪婪匹配

正则匹配默认是贪婪匹配,即匹配尽可能多的字符。若要采用非贪婪匹配,则需要在需要采用非贪婪匹配的字符后面加个?号。

如要匹配出数字后面的0

1
2
3
4
5
6
import re
print(re.match(r'^(\d+)(0*)$','102300').groups())#\d+采用的是贪婪匹配
print(re.match(r'^(\d+?)(0*)$','102300').groups())#\d+采用非贪婪匹配
#运行结果如下:
('102300', '')
('1023', '00')

编译

当在python中使用正则表达式时,re模块内部会干两件事:

  • 编译正则表达式,若正则表达式的字符串本身不合法,会报错
  • 用编译后的正则表达式取匹配字符串

若一个正则表达式需要重复使用几千次,处于效率考虑,可以对该正则表达式进行预编译,这样后面重复使用时就不需要编译了,可直接匹配。编译后生成Regular Expression对象,由于该对象已经包含了正则表达式,因而调用对应的方法时不用给出正则字符串。

1
2
3
4
5
6
7
8
import re
re_telephone=re.compile(r'^(\d{3})-(\d{3,8})$')#编译
#使用
print(re_telephone.match('010-12345').groups())
print(re_telephone.match('010-1225').groups())
#运行结果如下:
('010', '12345')
('010', '1225')

练习题

请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:

  • someone@gmail.com

  • bill.gates@microsoft.com

    代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
import re
def is_valid_email(addr):
if re.match(r'^([a-zA-Z\.]+)@([0-9a-zA-Z]+).com$',addr):
return True
assert is_valid_email('someone@gmail.com')
assert is_valid_email('bill.gates@microsoft.com')
assert not is_valid_email('bob#example.com')
assert not is_valid_email('mr-bob@example.com')
print('ok')

#运行结果如下:
ok

版本二可以提取出带名字的Email地址:

  • tom@voyager.org => Tom Paris
  • bob@example.com => bob
1
2
3
4
5
6
7
8
9
10
import re
def name_of_email(addr):
t=re.match(r'^<?([a-zA-Z]+\s?[a-zA-Z]+)>?\s?([a-zA-Z])*@([a-zA-Z]+\.[a-zA-Z]+)$',addr)
return t.group(1)
# 测试:
assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris'
assert name_of_email('tom@voyager.org') == 'tom'
print('ok')
#运行结果如下:
ok

访问数据库

使用SQLite

一个数据库连接称为connection,连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后获得执行结果。执行插入等操作后要调用commit()提交事务。

由于SQLite的驱动内置在python标准库中,故可以直接操作SQLite数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sqlite3#导入数据库
#连接到数据库,数据库文件是test.db。若文件不存在会自动在当前目录创建
conn=sqlite3.connect('test.db')
cursor=conn.cursor()#创建一个cursor
#print(cursor.execute('drop table user'))#删除user表
print(cursor.execute('create table user (id varchar(20) primary key, name varchar(20))'))#创建user表
print(cursor.execute('insert into user (id,name) values (\'1\',\'Michael\')'))#往表中插入一条记录
print(cursor.rowcount)#获得插入的行数
print(cursor.execute('select * from user where id=?', ('1',)))#查询表中记录
print(cursor.fetchall())#输出表中记录
conn.commit#提交事务
conn.close()#关闭Cursor
conn.close()#关闭连接

#运行结果如下:
<sqlite3.Cursor object at 0x0000027546A17BC0>
<sqlite3.Cursor object at 0x0000027546A17BC0>
1
<sqlite3.Cursor object at 0x0000027546A17BC0>
[('1', 'Michael')]

练习题

请编写函数,在Sqlite中根据分数段查找指定的名字:

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
import os, sqlite3

db_file = os.path.join(os.path.dirname(__file__), 'test.db')
if os.path.isfile(db_file):
os.remove(db_file)

# 初始数据:
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
#cursor.execute('drop table user')
cursor.execute('create table user(id varchar(20) primary key, name varchar(20), score int)')
cursor.execute(r"insert into user values ('A-001', 'Adam', 95)")
cursor.execute(r"insert into user values ('A-002', 'Bart', 62)")
cursor.execute(r"insert into user values ('A-003', 'Lisa', 78)")
conn.commit()


def get_score_in(low, high):
' 返回指定分数区间的名字,按分数从低到高排序 '
cursor.execute('select name from user where score between ? and ? order by score',(low,high))
return [n[0] for n in cursor.fetchall()]

# 测试:
assert get_score_in(80, 95) == ['Adam'], get_score_in(80, 95)
assert get_score_in(60, 80) == ['Bart', 'Lisa'], get_score_in(60, 80)
assert get_score_in(60, 100) == ['Bart', 'Lisa', 'Adam'], get_score_in(60, 100)
cursor.close()
conn.close()
print('Pass')

#运行结果如下:
Pass

使用MySQL

连接到MySQL服务器的test数据库的命令绝大部分与SQLite相同,仅在连接数据库的部分有所差别,注意MySQL的占位符是%s

1
2
3
4
5
6
7
8
# 导入MySQL驱动:
import mysql.connector
# 注意把password设为你的root口令:
conn =
mysql.connector.connect(user='root', password='password', database='test')
cursor = conn.cursor()
# 插入一行记录,注意MySQL的占位符是%s:
cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])

使用SQLAlchemy

SQLAlchemy是有名的ORM框架,其可以把数据库表的一行记录与一个对象相互做自动转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from tkinter.tix import Tree
from sqlalchemy import Column,String,create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base=declarative_base()#创建对象基类
class User(Base):#定义user对象
__tablename__='user'#表名
id=Column(String(20),primary_key=True)
name=Column(String(20))
engine=create_engine('mysql+mysqlconnector://root:password@localhost:3306/test') #初始化数据库连接
DBSession=sessionmaker(bind=engine)#创建DBSession类型
session=DBSession()#创建session对象
new_user=User(id='5',name='Bob')#创建新User对象
session.add(new_user)#添加到session
session.commit()#提交到数据库
session.close()#关闭session

查询数据如下:

1
2
3
4
5
6
7
8
9
10
session=DBSession()#创建session对象
#创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user=session.query(User).filter(User.id='5').one()
# 打印类型和对象的name属性:
print('type:',type(user))
print('name:',user.name)
session.close()
#运行结果如下:
type: <class '__main__.User'>
name: Bob
CATALOG
  1. 1. 正则表达式
    1. 1.0.1. 进阶
    2. 1.0.2. re模块
    3. 1.0.3. 切分字符串
    4. 1.0.4. 分组
    5. 1.0.5. 贪婪匹配
    6. 1.0.6. 编译
    7. 1.0.7. 练习题
  • 2. 访问数据库
    1. 2.1. 使用SQLite
      1. 2.1.1. 练习题
    2. 2.2. 使用MySQL
    3. 2.3. 使用SQLAlchemy