Week 8
本周学习主要内容包括python中的正则表达式(re模块),和ini文件,序列化与反序列化(部分)
习题
缓存应用场景
- 同样的函数参数一定得到同样结果,至少是一段时间内,同样输入得到同样结果
- 计算代价高,函数执行时间长
- 需要多次执行,每次计算代价高
- cache
key的存储
- 数据结构选取字典 key必须hashable
- key能使用位置传参和关键字传参提供的实参
- 位置传参本身有顺序
- 关键字传参可无序,可以用字典收集
- OrderedDict可以用,记录key的录入顺序
- 可以不使用OrderedDict,对字典kv对按照key排序
装饰器的用途
- 装饰器是AOP,面向切面编程(Aspect Oriented Programming)的思想的体现
- 面向对象需要通过继承或组合依赖等方式调用一些功能,而这些功能代码可能在多个类中出现,会造成代码重复增加耦合,而AOP在需要的类或方法上切下,前后切入点可加入增强功能,让调用者和被调用者解耦
- 不修改原有的业务代码,而给程序动态添加功能的技术
Python中的正则表达式
- Python中使用re进行正则表达式的编写
- 多行模式:re.M / re.MULTILINE
- 单行模式:re.S / re.DOTALL
编译
- re.compile(pattern,flags=0)
- 设定flags,编译模式,返回正则表达式对象regex
- pattern就是正则表达式字符串,flags是选项。正则表达式需要被编译,为了提高效率,这些编译后的结果被保存,下次使用同样pattern的时候就不需要再次编译。
单次匹配
- re.match(pattern,string,flags=0)
- regex.match(string[,pos[,endpos]])
- match匹配从字符串的开头匹配,regex对象match方法可以重设开始和结束位置,返回match对象
s = """bottle\nbag\nbig\napple"""
r = re.match('b.+',s,re.S) #单行模式,贪婪 包括\n都被匹配
#匹配上,得到match类型实例,span(start,end)
#匹配不上,返回None
#,match单次搜索,并不做全文搜索,而且要求必须是从头匹配 索引0
r = re.match('a',s)
print(r) #None
r = re.match('^a',s)
print(r) #? 默认模式,一整行,没有a开头 None
r = re.match('^a',s,re.M)
print(r) #? 多行模式,可以看作多行,但影响^和$,所以依旧返回None
- re.search(pattern,string,flags=0)
- regex.search(string[,pos[,endpos]])
- 从头搜索到第一个匹配,regex对象search方法可以重设开始和结束位置,返回match对象
#search是单次匹配,但是从index为0开始向后找到一次,找不到返回None
s = """bottle\nbag\nbig\napple"""
r = re.search('b',s)
print(type(r),r) #b 0 match实例
r = re.search('a',s) #index 0
print(type(r),r) #a 8
r = re.search('a',s,re.M)
print(type(r),r) #a 8
r = re.search('^a',s,re.M)
print(type(r),r) #a 15
r = re.search('^a',s) #index 0
print(type(r),r) #None
- re.fullmatch(pattern,string,flags=0)
- regex.fullmatch(string[,pos[,endpos]])
- 真个字符串和正则表达式匹配
#fullmatch 要完全匹配,多了少了都不行
s = """bottle\nbag\nbig\napple"""
r = re.fullmatch('b.+',s)
print(type(r),r) #None
r = re.fullmatch('bag',s)
print(type(r),r) #None
r = re.fullmatch('b.+',s,re.S)
print(type(r),r) #全部
r = re.fullmatch('b.+',s,re.M)
print(type(r),r) #None
全文搜索
- re.findall(pattern,string,flags=0)
- regex.findall(string[,pos[,endpos]])
- 对整个字符串,从左至右匹配,返回所有匹配项的列表
- re.finditer(pattern,string,flags=0)
- regex.finditer(string[,pos[,endpos]])
- 对整个字符串,从左至右匹配,返回所有匹配项,返回迭代器
- 注意每次迭代返回的是match对象
匹配替换
- re.sub(pattern,replacement,string,count=0,flags=0)
- regex.sub(replacement,string,count=0)
- 使用pattern对字符串string进行匹配,对匹配项使用repl替换
- replacement可以是string、bytes、function
#sub 模式替换,可以指定至多替换的次数,返回替换结果
s = """bottle\nbag\nbig\napple"""
r=re.sub('b\w+','magedu',s,10)
print(type(r),r)
--------------------------------
<class 'str'> magedu
magedu
magedu
apple
- re.subn(pattern,replacement,string,count=0,flags=0)
- regex.subn(replacement,string,count=0)
- 同sub返回一个元组(new_string,number_of_subs_made)
#subn 模式替换,可以指定至多替换的次数,返回一个元组 (替换结果,替换次数)
s = """bottle\nbag\nbig\napple"""
r=re.subn('b\w+','magedu',s,1)
print(type(r),r)
分组
- 使用小括号的pattern捕获的数据被放到了组group中
- match、search函数返回match对象;findall返回字符串列表;finditer返回一个个match对象
- 如果pattern中使用了分组,如果有匹配结果,会在match对象中
- 使用**group(N)**方式返回对应分组,1到N是对应的分组,0返回整个匹配的字符串,N不写缺省为0
- 如果使用了命名分组,可以使用**group(‘name’)**的方式取分组
- 也可以使用groups()返回所有组
- 使用groupdict()返回所有命名的分组
#group 分组,匹配是前提,要先有匹配对象
s = """bottle\nbag\nbig\napple"""
m = re.match('b\w+',s) #分组看正则表达式里有没有括号
print(m) #0,6 bottle
m = re.match('(b)(\w+)',s)
print(m.groups()) #取分组们
m = re.search('^(a)(\w+)',s,re.M)
print(m.groups())
m = re.search('^a\w+',s,re.M)
print(m.groups())
print(m)
m = re.search('^a\w+',s,re.M)
print(m.groups())
print(m.group(0)) #和m.groups()等价,组0表示match
print(m)
#print(m.group(1)) #空元组
#命名分组
s = """bottle\nbag\nbig\napple"""
m = re.search('a(?P<tail>\w+)',s,re.M)
if m:
print(m.groups()) #取分组们
print(m.group(0)) #空元组 和m.groups()等价,组0表示match
print(m)
print(m.group()) #组,组号,1开始
分割字符串
- 字符串的分割函数split,太难用,不能指定多个字符进行分割
- re.split(pattern,string,maxsplit=0,flags=0)
#split 分割,要指定分隔符
s = """bottle\nbag\nbig\napple"""
print(s.split()) # \s+
print(s.split('\n'))
print(s.split('\s+'))
print(s.split('\n\t '))
-----------------------------------------
['bottle', 'bag', 'big', 'apple']
['bottle', 'bag', 'big', 'apple']
['bottle\nbag\nbig\napple']
['bottle\nbag\nbig\napple']
CSV模块
- comma seperated values
- delimiter 列分隔符,逗号
- lineterminator 行分隔符, \r\n
- quotechar 字段的引用符号,缺省为“”双引号
- 双引号的处理:
doublequote:双引号的处理,默认为True。如果碰到数据中有双引号,而quotechar也是双引号,True则使用两个双引号表示,False表示使用转义字符将作为双引号的前缀
escapecahr:一个转义字符,默认为None
writer:=csv.writer(f,doublequote=False,escapechar=’@’)遇到双引号则必须提供转移字符
- quoting指定双引号的规则:
QUOTE_ALL 所有字段
QUOTE_MINIMAL 特殊字符字段,Excel方言使用该规则
QUOTE_NONNUMERIC 非数字字段
QUOTE_NONE 都不使用引号
ini文件处理
- 作为配置文件,ini文件格式很流行(MySql)
- 中括号里称为section,译作节、区、段;每个section内都是key=value形成的键值对,key称为option选项
- DEFAULT是缺省section的名字,必须大写
configparser
import configparser
cfg = configparser.ConfigParser()
#配置文件解析器对象
print(cfg)
r = cfg.read('mysql.ini')
print(r)
#section
for x in cfg.sections(): #返回是所有section,但不含特殊的缺省section
print(type(x),x)
print(cfg.options(x)) #每一个section下的options,包含缺省
print('='*30)
for k,v in cfg.items(): #items()迭代,包含DEFAULT
print(type(k),k) #k str
print(type(v),v) #v section对象
print(cfg.items(k)) #二元组克爹大对象,OrderedDict对象
print('-'*30)
print(cfg.has_option('mysqld','a'))
# 取值
t = cfg.get('mysql','a')
print(type(t),t)
t = cfg.get('mysqld','a')
print(type(t),t)
t = cfg.get('mysqld','port')
print(type(t),t)
t = cfg.getint('mysqld','port',fallback=3306) #return int(get())
print(type(t),t)
t = bool(cfg.get('mysqld','a'))
print(type(t),t)
t = cfg.get('mysql','b',fallback='123')
print(type(t),t)
cfg.set('mysqld','a','100') #内存中
if cfg.has_section('test'):
cfg.remove_section("test") #本section所有options全部删除
cfg.add_section('test')
print(cfg['mysql'])
cfg['test']['t1'] = '1000'
cfg['test']['t2'] = str([1,2,3])
t = cfg['test']['t2']
print(type(t),t)
print(cfg.get('test','t2'))
cfg['test'] = {'t3':'abc'}
print(cfg.options('test')) #
with open('t.ini','w') as f:
cfg.write(f)
print(cfg._sections)
for k,v in cfg._sections.items():
print(k,v)
序列化与反序列化
- 需要设计一套协议,按某种规则,把内存中数据保存到文件中。文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件,即序列化。
- 反之,从文件的字节序列恢复到内存并且还是原来的类型,就是反序列化。
定义
-
serialization:序列化
将内存中对象存储下来,把它们变成一个个字节 --> 二进制 -
deserialization:反序列化
将文件中的一个个字节恢复成内存中的对象 <-- 二进制 -
序列化保存到文件就是持久化。
-
可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。
-
python提供了pickle库
Pickle库
- dumps:对象序列化为bytes对象
- dump:对象序列化到文件对象,就是存入文件
- loads:从bytes对象反序列化
- load:对象反序列化,从文件读取数据
import pickle
filename = 'c:/ser'
i = 99
c = 'c'
l = list('123')
d = {'a':1,'b':'abc','c':[1,2,3]}
#序列化
with open(filename,'wb') as f:
pickle.dump(i,f)
pickle.dump(c,f)
pickle.dump(l,f)
pickle.dump(d,f)
#反序列化
with open(filename,'rb') as f:
print(f.read(),f.seek(0))
for i in range(4):
x = pickle.load(f)
print(x,type(x))
#对象序列化
import pickle
class AAA:
tttt = 'ABC' #类属性
def show(self): #def 方法
print("aabbcc")
a1 = AAA() #实例化 得到了一个AAA类型的实例 a1是引用类型
#序列化
ser = pickle.dumps(a1) #bytes
print('ser = {}'.format(ser)) #没有序列化他们 为什么可以打印出他们?
#with open('c:/ser','wb') as f:
# f.write(ser)
obj = pickle.loads(ser)
print(type(obj)) #返回的是类型 AAA
print(obj) #AAA object对象实例,我在内存中什么位置
print(obj.tttt)
obj.show()
----------------------------------------
ser = b'\x80\x03c__main__\nAAA\nq\x00)\x81q\x01.'
<class '__main__.AAA'>
<__main__.AAA object at 0x000001F5EAE43588>
ABC
aabbcc
- 上例中使用连续AAA、ABC、abc等字符串就是为了在二进制文件中能容易地发现他们
- 上例中其实就是保存了一个类名,因为所有的其他东西都是由类定义,是不变的,所以只序列化一个AAA类名,反序列化时找到类就可以恢复一个对象
import pickle
#对象序列化
class AAA:
tttt = 'ABC'
def __init__(self): #初始化方法
print('init~~~~')
self.cccc = 'AABBCC' #实例的属性,每一个实例都不同
a1 = AAA() #实例化 创建AAA类的对象 会调用__init___
#序列化
ser = pickle.dumps(a1) #bytes
print('ser = {}'.format(ser)) #没有序列化他们 为什么可以打印出他们?
#反序列化
obj = pickle.loads(ser)
print(type(obj)) #返回的是类型 AAA
print(obj) #AAA object对象实例,我在内存中什么位置
print(obj.tttt) #ABC
print(obj.cccc) #AABBCC
- 上例中除了必须保存的AAA,还序列化了cccc和AABBCC,因为这是每个对象自己的属性,每一个对象是不一样的,所以这些数据需要序列化。