python 系统编程
- TAGS: Python
Python 异常处理与异常机制
主要内容:
- 导演的工作机制
- 异常的语法
- Python异常类结构
- 捕获异常
- 抛出与转移异常
- Try/except/else 理解与使用
- Try/finally 理解与使用
- With 语句及其异常域内存管理机制
序列化和反序列化
主要内容:
- 序列化、反序列化重要性
- Python 序列化库 Pickle
- 文本序列化方案 Json
- 高效二进制序列化库 Msgpack
- 常用序列化方案比较和适用场景
Python 文件系统操作编程
主要内容:
- 文件打开与文件描述符
- 文件的编码与UNICODE
- 二进制文件与文本
- 文本文件的读写
- 二进制文件的读写
- 文件的迭代器读写方式
- 文件读写的异常与 With 语句
- 目录与文件遍历
- 目录创建与删除
- 目录与文件存在判定
- 目录与文件属性查看
- StringIO 和 ByteIO 使用
- 高级文件操作模块 Shutil 使用
- CSV 文件读写与解析
- ini 配置文件的读写与解析
文件操作
冯诺依曼体系架构
CPU由运算器和控制器组成:
- 运算器,完成各种算数运算、逻辑运算、数据传输等数据加工处理
- 控制器,控制计算机各部件协调运行
- 存储器,用于记忆程序和数据,例如内存
- 输入设备,将数据或程序输入到计算机中,例如键盘、鼠标
- 输出设备,将数据或程序的处理结果展示给用户,例如显示器、打印机等
一般说IO操作,指的是文件IO,如果指的是网络IO,都会直接说网络IO
磁盘目前依然是文件持久化最重要设备。、
1956年,IBM发明可以存储5MB的磁盘驱动器。磁盘使用磁性材料记录数据,使用NS极表示0或1。
现代磁盘使用温彻斯特磁盘驱动器,如下
温彻斯特磁盘拥有多个同轴金属盘片,盘片上涂上磁性材料,一组可移动磁头。运行时,磁头摆动从飞速旋转的盘片读取或写入数据。一般常见盘片 转速 为5400转/分钟、7200转/分钟等。磁头摆动定位数据过程称为 寻道 。转速、寻道都影响磁盘读写性能。
磁头不和盘片接触,磁盘内部也不是真空,磁头悬浮在磁盘上方。一组磁头从0编号。
盘片Platter: 涂满磁性材料的盘片,早期是金属的,现在多为玻璃。
磁道Track:盘片划分同心圆,同心圆间距越小,一般来说存储容量越大。磁道编号由外向内从0开始。
扇区Sector:一个磁道上能够存储的0或1信号太多了,所以需要进一步划分成若干弧段,每一个弧段就是一个扇区。通常一个扇区为512字节。每个扇区都有自己的编号。
柱面Cylinder:从垂直方向看,磁道的同心圆就构成了圆柱。编号由外向内从0开始。
注意:盘片内圈扇区面积小,外圈面积大。但扇区都是512字节。外圈磁密度低,内圈磁密度高。
如今磁盘,可以做到磁密度一致,所以磁盘划分,外圈可以划分更多扇区,这样可以存储更多数据。这种分区弧长一致的分配扇区方法称为ZBR(Zoned-bit recording),按照半径分成不同的磁道组称为Zone。这样,转动同样的角度,外圈读取数据更多。
簇Cluster:操作系统为了优化IO,使用磁盘空间时最小占用的多个扇区。一般簇大小为4-64个扇区(2KB-32KB)。所以,一个文件就有了物理占用空间大小即实际分得的磁盘空间,和逻辑占用大小即文件本身的实际字节数。往往实际占用空间都比文件本身大。越是小文件,越浪费空间。
文件IO常用操作
- open 打开
- read 读取
- write 写稿
- close 关闭
- readline 行读取
- readlines 多行读取
- seek 文件指针操作
- tell 指针位置
打开操作
open 方法
open( file, #文件对象,路径 mode='r', #模式 buffering=-1, #缓冲区 encoding=None, #编码 errors=None, #出错怎么办 newline=None, #遇到换行符怎么办 closefd=True, #是否关闭文件描述符 opener=None, #打开的是谁 )
打开一个文件,返回一个文件对象(流对象)和文件描述符。打开文件失败,则返回异常
基本使用:
创建一个文件test,然后打开它,用完 关闭
# coding: utf-8 #open('test') #报错,当前路径不存在,FileNotFoundError: [Errno 2] No such file or directory: 'test' f = open('test') # open成功后,一定可以拿到一个文件对象,就可以对它进行增删改查, 文件操作,读写 #文件对象,默认是文件打开方式 #encoding='cp936' #windows默认GBK,codepage代码页 936 等价认为是GBK #encoding='UTF-8' #linux系统默认都是用utf-8 中文大多数3字节 f.close #使用完后关闭
文件操作中,最常用的操作就是读和写。
文件访问的模式有两种:文本模式和二进制模式。不同模式下,操作函数不尽相同,表现结果也不一样。
注:windows中使用codepage代码页,可以认为每一个代码页就是一张编码表。cp936等同于GBK。
open的参数
file
打开或要创建的文件。如果不指定路径,默认是当前路径。
mode模式
模式描述字符意义:
- r 缺省模式,只读打开。文件不存在,立即报错。文件存在,从头开始读,文件指针初始在开头即索引0的位置。
- w 只写打开,不支持读取。文件存在,文件内容将被清空,从头写;文件不存在,文件被创建,从头写。白纸一张,从头写,初始位置在0
- x 创建并写入一个新文件,不支持读取。文件存在,抛异常;文件不存在不报错,新文件被创建
- a 只写打开,追加内容,不支持读取。文件存在,追加写入,文件不存在,创建文件,从头写
- b 二进制模式。字节流,将文本按照字节理解,与字符编码无关。二进制模式操作时,字节操作使用bytes类型。不能单独使用,必须和rwax四种之一配合使用,不影响主模式。
- t 缺省模式,文本模式。text,默认可以不写。不能单独使用,必须和wrax配合使用,不影响主模式
- + 读或写打开后,使用+来增加缺失的写或读的能力。必须和主模式(rwax)配合使用,不影响主模式。
- r+ => rt+ 只读文本模式打开,补充写能力。文件指针在开头。
- w+ => wt+ 只写文本模式打开,补充读能力。w能力不变,会清空所有内容
在上面的例子中,可以看到默认是文本打开模式,且是只读的。
# coding: utf-8 #mode rwxabt+ #r 只读模式 f = open('D:/tmp/test') print(f) #: <_io.TextIOWrapper name='D:/tmp/test' mode='r' encoding='cp936'> print(f.readable()) #: True 可读的 r readonly print(f.read()) #文件空的 #f.write('abc') #报错 io.UnsupportedOperation: not writable f.close() f = open('D:/tmp/test', 'r') #只读 #f.write('abc') #报错 io.UnsupportedOperation: not writable f.close() #w写模式 f = open('D:/tmp/test1', 'w') #test1 不存在 print(f.write('abc')) #返回值3,3表示写入的字符数?字节数? #print(f.write('啊')) #返回1,目前返回值是表示字符数 #f.read() #报错,w模式是只写 f.close() f = open('D:/tmp/test1', 'w') #test1存在,w打开,内容没了 f.write('xyz') f.close() #a模式 f = open('D:/tmp/test1', 'a') #test1存在 #f.read() #a模式不支持读取 f.write('123') #追加写入 f.close() f = open('D:/tmp/test2', 'a') #test1不存在,创建文件,从头写 #a模式,不管文件存在与否,都从尾部追加写入 f.write('123\n') f.write('789') f.close() #x模式 #f = open('D:/tmp/test', 'x') #x模式 exsits,文件存在,报错 f = open('D:/tmp/testx', 'x') #x模式 exsits,文件不存在,文件被创建 #f.read() #x模式不支持读取 f.write('testx test') f.close() #t模式 text f = open('D:/tmp/testx', 'r') #字符和编码有关,字符串的世界都是由编码的 #rwax 默认都用文本模式打开 rt wt at xt,不能独立使用 print(f) #: <_io.TextIOWrapper name='D:/tmp/testx' mode='r' encoding='cp936'> f.close() #b模式,binnary, 二进制 f = open('D:/tmp/testx', 'rb') #ab, wb print(f) # encoding 字节的世界都是0和1组成的字节,和编码无关 #: <_io.BufferedReader name='D:/tmp/testx'> print(f.read()) #返回字节 : b'testx test' f.close() f = open('D:/tmp/testx', 'wb') #ab, wb f.write(b'xyz') #返回一个值,写入的字节数 f.write('zhongwen'.encode()) #缺少编码,utf-8,二进制模式,跟字符无关 f.close() #+模式 #+ 附加的能力,必须和主模式(rwax)配合使用,不影响主模式。r+ w+ a+ x+ f = open('D:/tmp/testx', 'r+') f.read() f.write('xyz') #可写 f.close() #注意文件指针
文件指针
文件指针,指向当前字节位置。
- mode=r,指针起始在0
- mode=a,指针起始在EOF
tell()显示指针当前位置
seek(offset[,whence]) 移动文件指针位置。offset偏移多少字节,whence从哪里开始。
文本模式下
- whence 0 缺省值,表示从头开始,offset只能正整数
- whence 1 表示从当前位置,offset只接受0
- whence 2 表示从EOF开始,offset只接受0
二进制模式下
- whence 0 seek(0) seek(0, 0) seek(x) x >=0
- whence 1 表示从当前位置,offset可正可负,不能左超界
- whence 2 表示从EOF开始,offset可正可负,不能左超界
# coding: utf-8 f = open('D:/tmp/test', 'w+') print(f.tell()) #: 0 print(f.write('abc')) #: 3 写入了3个字符 print(f.tell()) #: 3 指针移动了,返回的一直是字节的索引 print(f.write('de')) #: 2 写入了2个字符 print(f.tell()) #: 5 指针移动了,当前位置在5 f.close() #以文本方式打开 f = open('D:/tmp/test') print(f.tell()) #: 0 print(f.read(2)) #: ab print(f.tell()) #: 2 print(f.read(1)) #: c print(f.tell()) #: 3 f.seek(0) #设置指针位置 print(f.read(1)) #: a print(f.tell()) #: 1 print(f.read()) #bcde 从当前位置到结尾 f.close() #seek f = open('D:/tmp/test') f.seek(0, 0) #第一个0 表示偏移,第二个0 表示whence 0表示从头开始,拉回到开头。whence 0 是默认的 print(f.read()) #abcde f.seek(0) print(f.read(2)) #ab f.seek(4, 0) print(f.read()) #e f.seek(0, 2) #跳到EOF #f.seek(-2, 0) # 报错,超出左界,不可以 f.seek(20, 0) #向右超界允许 #二进制模式 seek f = open('D:/tmp/test', 'rb') f.seek(2) #f.seek(-2) #f.seek(-2, 0) 由于左超界,所以报错 f.seek(-2, 1) #当前位置向左走2, 但不能超界,否则报错 print(f.tell()) #0 f.seek(100, 1) print(f.read()) #b'' 二进制世界,没有什么乱码,是字节就读
二进制模式支持任意起点的偏移,从头、从尾、从中间位置开始。
向后seek可以超界,但是向前seek的时候,不能超界,否则抛异常
buffering缓冲区
-1 表示使用缺省大小的buffer。如果是二进制模式,使用 io.DEAFAULT_BUFFER_SIZE
值,默认是4096或8192.如果是文本模式,如果是终端设备,是行缓存方式,如果不是,则使用二进制模式的策略。
- 0,只在二进制模式使用,表示关buffer
- 1,只在文本模式使用,表示使用行缓冲。意思就是见到换行符就flush
- 文本模式,如果缓冲区撑破了,被迫写入磁盘
- 二进制模式,1无效,默认缓冲区
- 大于1用于指定buffer的大小
- 文本模式,默认缓冲区
- 二进制模式,指定大小字节的缓冲区
buffer 缓冲区
缓冲区一个内存空间,一般来说是一个FIFO队列,到缓冲区满了或者达到阈值,数据才会flush到磁盘。
fush() 将缓冲区数据写入磁盘
close() 关闭前会调用flush()
io.DEAFAULT_BUFFER_SIZE
缺省缓冲区大小,字节
先看二进制模式
# coding: utf-8 # 缓冲区大小为0 f = open('D:/tmp/test', 'w+b', 0) #文本模式不支持,只在二进制模式,立即写入磁盘 f.write(b'a') f.write(b'b') print(f.read()) #返回空,但文本已被写入内容 f.seek(0) print(f.read()) #: b'ab' f.close() # 缓冲区大小为1 f = open('D:/tmp/test', 'w+', 1) #只在文本模式,使用行缓冲。见到换行符 \n 就flush f.write('a') f.write('a') print(f.read()) f.write('\n\t\n') print(f.read()) f.seek(0) print(f.read()) f.close() #如果缓冲区撑破了,被迫写入磁盘 f = open('D:/tmp/test', 'w+', 1) #只在文本模式,使用行缓冲。见到换行符 \n 就flush f.write('a' * 8100) f.write('b' * 100) f.close() import io print(io.DEFAULT_BUFFER_SIZE) #: 8192 f = open('D:/tmp/test', 'wb+', 1) #对二进制来讲,1无效, f.write(b'a') f.close() # 设置缓冲区大小 f = open('D:/tmp/test', 'wb+', 2) #对二进制来讲 f.write(b'a') f.write(b'b') f.close()
一般来说,只需要记得:
- 文本模式,一般都用默认缓冲区大小
- 二进制模式,是一个个字节的操作,可以指定buffer大小
- 一般来说,默认缓冲区大小是个比较好的选择,除非明确知道,否则不调整它
- 一般编程中,明确知道需要写磁盘了,都会手动调用一次flush,而不是等到自动flush或者close的时候。
encoding
编码,仅文本模式使用
None 表示使用缺省编码,依赖操作系统。windows 下缺省 GBK(0xB0A1), Linux下缺省UTF-8(0xE5 95 8A)
errors
什么样的编码错误将被捕获
None和'strict' 表示有编码错误将抛出ValueError异常;'ignore'表示忽略
newline
文本模式中,换行的转换。取值可以为None、''空串、'\r'、'\n'、'\r\n'
读时
- None,缺省值,表示'\r'、'\n'、'\r\n' 都被转换为 '\n'
- ''表示不会自动转换通用换行符,常见换行符都可以认为是换行
- 其它合法换行字符表示按照该字符分行
写时
- None,缺省值,表示'\n'都会被替换为系统缺省行分隔符os.linesep
- '\n' 或''空串表示'\n' 不替换
- 其它合法换行字符表示文本中的'\n'换行符会被替换为指定的换行字符
# coding: utf-8 #newline 新行, \n \r\n txt = 'python\rpip\nxx.com\r\nok' print(txt) #写入 txt = 'python\rpip\nxx.com\r\nok' f = open('D:/tmp/test', 'w+') f.write(txt) #做了一些事,newline #txt = 'python\rpip\r\nxx.com\r\r\nok' #None,把\n换成当前操作系统的默认换行符,windows \r\n f.close() #读取 f = open('D:/tmp/test') #newline=None t = f.read() print(t.encode()) #: b'python\npip\nxx.com\n\nok' # None,\r \n \r\n => \n 把常见换行符换成\n f.seek(0) print(f.readlines())#: ['python\n', 'pip\n', 'xx.com\n', '\n', 'ok'] f.close f = open('D:/tmp/test', newline='') #newline='' 什么都不干,按照各系统换行处理 t = f.read() print(t.encode()) #: b'python\rpip\r\nxx.com\r\r\nok' f.seek(0) print(f.readlines()) #: ['python\r', 'pip\r\n', 'xx.com\r', '\r\n', 'ok'] f.close f = open('D:/tmp/test', newline='\n') #newline='\n' 什么都不干,文件处理按\n处理 t = f.read() print(t.encode()) #: b'python\rpip\r\nxx.com\r\r\nok' f.seek(0) print(f.readlines()) #: ['python\rpip\r\n', 'xx.com\r\r\n', 'ok'] f.close f = open('D:/tmp/test', newline='\r') #newline='\r' 什么都不干,文件处理按\r处理 t = f.read() print(t.encode()) #: b'python\rpip\r\nxx.com\r\r\nok' f.seek(0) print(f.readlines()) #: ['python\r', 'pip\r', '\nxx.com\r', '\r', '\nok'] f.close f = open('D:/tmp/test', newline='\r\n') #newline='\r\n' 什么都不干,文件处理按\r\n处理 t = f.read() print(t.encode()) #: b'python\rpip\r\nxx.com\r\r\nok' f.seek(0) print(f.readlines()) #: ['python\rpip\r\n', 'xx.com\r\r\n', 'ok'] f.close
: python pip : xx.com : ok #test文件内容 python pip xx.com ok
python pip xx.com ok
其它
closefd 关闭文件描述符,True表示关闭它。False会在文件关闭后保持这个描述符。fileobj.fileno()查看
# coding: utf-8 #open( # file, #文件对象,路径 # mode='r', #模式 # buffering=-1, #缓冲区 # encoding=None, #编码 # errors=None, #出错怎么办 # newline=None, #遇到换行符怎么办 # closefd=True, #是否关闭文件描述符 # opener=None, #打开的是谁 #) f = open('D:/tmp/test') print(f.fileno()) #文件描述符 f.close() #关闭后,文件描述符关闭 print(f.closed) #是否关闭 #: 3 #: True
read
read(size=-1)
size表示读取的多少字符或字节;负数或者None表示读取到EOF
行读取
readline(size=-1)
一行行读取文件内容。size设置一次能读取行内几个字符或字节
readlines(hint=-1)
读取所有行的列表。指定hint则返回指定的列表。
常用的读取方式
# coding: utf-8 #按行迭代 f = open('D:/tmp/test') #返回可迭代对象,惰性的 for line in f: print(line.strip()) f.close()
write
write(s),把字符串s写入到文件中并返回字符的个数
writelines(lines),将字符串列表写入文件。
# coding: utf-8 f = open('d:/tmp/test', 'w+') lines = ['abc', '123\n', 'cici'] #提供换行符 f.writelines(lines) #writelines 但是不解决换行问题 f.seek(0) print(f.read()) f.close() #: abc123 #: cici
close
flush 并关闭文件对象
文件已经关闭,再次关闭没有任何效果
其他
- seekable() 是否可seek
- readable() 是否可读
- writeable() 是否可写
- closed 是否已经关闭
上下文管理
问题的引出
在Linux中,执行
# coding: utf-8 #下面必须这么写 lst = [] for _ in range(2000): lst.append(open('test')) # OSError: [Error 24] Too many open files: 'test' print(len(lst))
lsof 列出打开的文件。 yum install lsof
安装
ps aux |grep python
lsof -p 9255|grep test| wc -l
ulimit -a
上下文管理
上下文管理:
- 使用with关键字,上下文管理针对的是with后的对象
- 可以使用as关键字
- 上下文管理的语句块并不会开启新的作用域
文件对象上下文管理
- 进入with时,with后的文件对象是被管理对象
- as子句后的标识符,指向with后的文件对象
- with语句块执行完的时候,会自动关闭文件对象
解决
# coding: utf-8 f = open('d:/tmp/test') try: print(f.closed) f.write('abc') print(f.read()) finally: f.close() print(f.closed)
# coding: utf-8 with 对象 [as 标识符]: pass
# coding: utf-8 with open('d:/tmp/test') as f: #在文件对象with它,as f,f 就是该文件对象 f = open('d:/tmp/test') print(f) print(f.closed) #是不关闭 print(f.read()) #with语句块结束了,自动调用该文件对象的close方法,出错时也会调用close方法 f = open('d:/tmp/test') with f: print(f) print(f.closed) print(f.read()) #哪怕出现异常,离开with时,依然调用with后文件对象的close方法 print(f.closed)
- 对象类似于文件对象的IO对象,一般来说都需要在不使用的时候关闭、注销,以释放资源
- IO被打开的时候,会获得一个文件描述符。计算机资源是有限的,所以操作系统都会做限制。就是为了保护计算机的资源不要被完全耗尽,计算资源是很多程序共享的,不是独占的。
- 一般情况下,除非特别明确的知道当前资源情况,否则不要盲目提高资源的限制值解决问题。
StringIO和BytesIO
StringIO
- io模块中的类
- from io import StringIO
- 内存中,开辟的一个 文本模式 的buffer,可以像文件对象一样操作它
- 当close方法被调用的时候,这个buffer会被释放
getvalue()获取全部内容。跟文件指针没有关系。
好处
一般来说,磁盘的操作比内存的操作要慢得多,内存足够的情况下,一般的优化思路是少 落地 ,减少磁盘IO的过程,可以大大提高程序的运行效率。
BytesIO
- io模块中类
- from io import BytesIO
- 内存中,开辟的一个二进制模式的buffer,可以像文件对象一样操作它
- 当close方法被调用的时候,这个buffer会被释放
# coding: utf-8 from io import StringIO, BytesIO s = StringIO() #IO缓冲区,文本 print(s.seekable(), s.readable(), s.writable()) print(s.read()) print(s.write('abc')) print(s.read()) #指针 s.seek(0) print(s.readlines()) #[] #print(s.fileno()) #没有文件描述符,没有使用。内部中东西 print(s.read()) #上面读完就读不出东西 print('-' * 30) print(s.getvalue()) #内存中建立类文件对象,无视指针,直接获取全部数据 s.close() #True True True # #3 # #['abc'] # #------------------------------ #abc s = BytesIO() #IO缓冲区,文本 print(s.seekable(), s.readable(), s.writable()) print(s.read()) print(s.write(b'abc')) print(s.read()) #指针 s.seek(0) print(s.readlines()) #[] #print(s.fileno()) #没有文件描述符,没有使用。内部中东西 print(s.read()) #上面读完就读不出东西 print('-' * 30) print(s.getvalue()) #内存中建立类文件对象,无视指针,直接获取全部数据 s.close() #True True True #b'' #3 #b'' #[b'abc'] #b'' #------------------------------ #b'abc' s = BytesIO() #IO缓冲区,文本 with s: print(s.seekable(), s.readable(), s.writable()) print(s.read()) print(s.write(b'abc')) print(s.read()) #指针 s.seek(0) print(s.readlines()) #[] #print(s.fileno()) #没有文件描述符,没有使用。内部中东西 print(s.read()) #上面读完就读不出东西 print('-' * 30) print(s.getvalue()) #内存中建立类文件对象,无视指针,直接获取全部数据 #s.close() print(s.closed)
flie-like对象
- 类文件对象,可以像文件对象一样操作
- socket对象、输入输出对象(stdin、stdout) 都是类文件对象
# coding: utf-8 from sys import stderr, stdout #0 stdin; 1 stdout; 2 stderr f = stdout #可写 print(f.seekable(), f.writable(), f.readable()) f.write('abc') print('xzy') #默认stdout #: True True False #: abcxzy f1 = stderr print(f1.seekable(), f1.writable(), f1.readable()) f1.write('123') print('456', file=stderr) with open('d:/tmp/test', 'w') as f: print('abcdef\n1234', file=f) #写到文件中 #f.close()
正则表达式基础
概述
perl文官正则: https://perldoc.perl.org/perlre 基础正则表达式: https://www.cnblogs.com/f-ck-need-u/p/9621130.html Perl正则表达式超详细教程: https://www.cnblogs.com/f-ck-need-u/p/9648439.html vs code 中.NET正则: https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expressions grep 中正则: https://www.gnu.org/software/grep/manual/grep.html#Regular-Expressions python re正则表达式: https://docs.python.org/zh-cn/3/library/re.html
正则表达式,Regular Expression,缩写为regex、regexp、RE等
正则表达式是文本处理极为重要的技术,用它可以对字符串按照某种规则进行检索、替换。
1970年代,Unix之父Ken Thompson将正则表达式引入到Unix中文本编辑器ed和grep命令中,由此正则表达式普及开来。
1980年后,perl语言对HenrySpencer编写的库,扩展了很多新的特性。1997年开始,Philip Hazel开发出了PCRE (Perl Compatible Regular Expressions),它被PHP和HTTPD等工具采用。
正则表达式应用极其广泛,shell中处理文本的命令、各种高级编程语言都支持正则表达式。
参考 https://www.w3cschool.cn/regex_rmjc/
正则测试工具RegEx Tester: https://sourceforge.net/projects/regextester/ 在线测试:https://regextester.github.io/
正则表达式是一个特殊的字符序列,用于判断一个字符串是否与我们所设定的字符序列是否匹配,也就是说检查一个字符串是否与某种模式匹配。
正则表达式可以包含普通或者特殊字符。绝大部分普通字符,比如 'A' , 'a' , 或者 '0' ,都是最简单的正则表达式。它们就匹配自身。你可以拼接普通字符,所以 cici 匹配字符串 'cici' .
一些字符,如 '|'
or '('
,是特殊的。特殊字符要么代表普通字符的类别,要么影响它们周围的正则表达式的解释方式。正则表达式模式字符串可能不包含空字节,但可以使用 \number 符号指定空字节,例如 '\x00' .
重复修饰符 ( * , + , ? , {m,n} , 等) 不能直接嵌套。这样避免了非贪婪后缀 ? 修饰符,和其他实现中的修饰符产生的多义性。要应用一个内层重复嵌套,可以使用括号。 比如,表达式 (?:a{6})*
匹配6个 'a' 字符重复任意次数。
分类
- BRE 基本正则表达式,grep、sed、vi等软件支持。vim有扩展
- ERE 扩展正则表达式,egrep(grep -E) 、sed -r等
- PCRE 几乎所有高级语言都是PCRE的方言或变种。Python从1.6开始使用SRE正则表达式引擎,可以认为是PCRE的子集,见模块re。
基本语法
元字符
metacharacter https://www.cnblogs.com/f-ck-need-u/p/9621130.html#%E5%85%83%E5%AD%97%E7%AC%A6
. 匹配除换行符外的任意一个字符。如. [abc] 字符集合,只能表示一个字符位置。匹配所包含的任意一个字符。如[abc]匹配plain中的'a' [^abc] 字符集合,只能表示一个字符位置。匹配除去集合内字符的任意一个字符。如 [^abc]可以匹配plain中的'p'、'l'、'i' 或者'n' [a-z] 字符范围,也是个集合,表示一个字符位置 ;匹配所有包含的任意字符。常用[A-Z][0-9] [^a-z] 字符范围,也是个集合,表示一个字符位置;匹配除去集合内字符的任意一个字符。 \b 匹配单词的边界。如\bb在文本中找到单词中b开头的b字符 \B 不匹配单词的边界。如t\B 包含t的单词但是不以t结尾的t字符,例如write; \Bb不以b开头的含有b的单词,例如able \d [0-9]匹配1位数字 \D [^0-9]匹配1位数字 \s 匹配1位空白字符,包含换行符、制表符、空格 [\f\r\n\t\v] \S 匹配1位非空白字符 \w 匹配[a-zA-Z0-9_] \W 匹配\w之外的字符
转义
凡是在正则表达式中有特殊意义的符号,如果想使用它的本意,请使用\转义。
反斜杠自身,得使用
\r、\n还是转义后代表回车、换行
重复
https://www.cnblogs.com/f-ck-need-u/p/9621130.html#%E5%85%83%E5%AD%97%E7%AC%A6
* 表示前面的正则表达式会重复0次或多次。如e\w*单词中e后面可以有非空白字符 + 表示前面的正则表达式重复至少1次。如e\w+单词中e后面至少有一个非空白字符 ? 表示前面的正则表达式会重复0次或1次。如e\w?单词中e后面至多有一个非空白字符 {n} 重复固定的n次。如e\w{1} 单词中e后面只能有一个非空白字符 {n,} 重复至少n次。如 e\w{1,} => e\w+ e\w{0,} => e\w* e\w{0,1} => e\w? {n,m} 重复n到m次。如e\w{1,10}单词中e后面至少1个,至多10个非空白字符
比如在一段字符串中寻找是否含有某个字符或某些字符,通常我们使用内置函数来实现,如下:
# 设定一个常量 a = 'CiCi|twowater|liangdianshui|网络安全|ReadingWithU' # 判断是否有 “CiCi” 这个字符串,使用 PY 自带函数 print('是否含有“CiCi”这个字符串:{0}'.format(a.index('CiCi') > -1)) print('是否含有“CiCi”这个字符串:{0}'.format('CiCi' in a))
输出的结果如下:
是否含有“CiCi”这个字符串:True 是否含有“CiCi”这个字符串:True
那么,如果使用正则表达式呢?
刚刚提到过,Python 给我们提供了 re 模块来实现正则表达式的所有功能,那么我们先使用其中的一个函数:
re.findall(pattern, string[, flags])
该函数实现了在字符串中找到正则表达式所匹配的所有子串,并组成一个列表返回,具体操作如下:
import re # 设定一个常量 a = 'CiCi|twowater|liangdianshui|网络安全|ReadingWithU' # 正则表达式 findall = re.findall('CiCi', a) print(findall) if len(findall) > 0: print('a 含有“CiCi”这个字符串') else: print('a 不含有“CiCi”这个字符串')
输出的结果:
['CiCi']
a 含有“CiCi”这个字符串
从输出结果可以看到,可以实现和内置函数一样的功能,可是在这里也要强调一点,上面这个例子只是方便我们理解正则表达式,这个正则表达式的写法是毫无意义的。为什么这样说呢?
因为用 Python 自带函数就能解决的问题,我们就没必要使用正则表达式了,这样做多此一举。而且上面例子中的正则表达式设置成为了一个常量,并不是一个正则表达式的规则,正则表达式的灵魂在于规则,所以这样做意义不大。
那么正则表达式的规则怎么写呢?先不急,我们一步一步来,先来一个简单的,找出字符串中的所有小写字母。首先我们在 findall 函数中第一个参数写正则表达式的规则,其中 [a-z] 就是匹配任何小写字母,第二个参数只要填写要匹配的字符串就行了。具体如下:
import re # 设定一个常量 a = 'CiCi|twowater|liangdianshui|网络安全|ReadingWithU' # 选择 a 里面的所有小写英文字母 re_findall = re.findall('[a-z]', a) print(re_findall)
输出的结果:
['i', 'i', 't', 'w', 'o', 'w', 'a', 't', 'e', 'r', 'l', 'i', 'a', 'n', 'g', 'd', 'i', 'a', 'n', 's', 'h', 'u', 'i', 'e', 'a', 'd', 'i', 'n', 'g', 'i', 't', 'h']
这样我们就拿到了字符串中的所有小写字母了。
或
x|y 匹配x或者y
wood took foot food 使用 w|food 或者 (w|f)ood
捕获
(pattern)
使用小括号指定一个子表达式,也叫分组; 捕获后会自动分配组号 从1开始 可以改变优先级
\数字
匹配对应的分组
例: (very) \1 匹配very very,但捕获的组group是very
(?:pattern)
如果仅仅为了改变优先级,就不需要捕获分组
例: (?:w|f)ood
'industr(?:y|ies)等价 'industry|industries'
(?<name>exp) (?'name'exp)
命名分组捕获,但是可以通过name访问分组
Python语法必须是(?P<name>exp)
断言
环视锚定
"环视"锚定,即lookaround anchor,也称为”零宽断言”,它表示匹配的是位 置,不是字符。
零宽断言
测试字符串为wood took foot food
(?=exp)
零宽度正预测先行断言。即从左向右的顺序环视
断言exp一定在匹配的右边出现,也就是说断言后面一定跟个exp
例:f(?=oo) f后面一定有oo出现
(?<=exp)
零宽度正回顾后发断言。即从右向左的逆序环视
断言exp一定出现在匹配的左边出现,也就是说前面一定有个exp前缀
例:(?<=f)ood、(?<=t)ook分别匹配ood、ook,ook前一定有t出现
负向零宽断言
(?!exp)
零宽度负预测先行断言。从左向右顺序环视的取反
断言exp一定不会出现在右侧,也就是说断言后面一定不是exp
例:
\d{3}(?!\d)匹配3位数字,断言3位数字后面一定不能是数字
foo(?!d) foo后面一定不是d
(?<!exp)
零宽度负回顾后发断言。从右向左逆序环视的取反
断言exp一定不能出现在左侧,也就是说断言前面一定不能是exp
例:(?<!f)ood ood的左边一定不是f
(?#comment) 注释
例:f(?=oo)(?#这个后断言不捕获)
注意:
断言会不会捕获呢?也就是断言占不占分组号呢?
断言不占分组号。断言如同条件,只是要求匹配必须满⾜断言的条件。
分组和捕获是同一个意思。
使用正则表达式时,能用简单表达式,就不要复杂的表达式。
范例:
import re a = 'wood took foot food' findall = re.findall('f(?=oo)', a) print(findall) # ['f', 'f']
贪婪与非贪婪
默认是贪婪模式,也就是说尽量多匹配更长的字符串。
非贪婪很简单,在重复的符号后面加上一个?问号,就尽量的少匹配了。
代码 说明 举例 *? 匹配任意次,但尽可能少重复 +? 匹配至少1次,,但尽可能少重复 ?? 匹配0次或1次,,但尽可能少重复 {n,}? 匹配至少n次,但尽可能少重复 {n,m}? 匹配至少n次,至多m次,但尽可能少重复
very very happy 使用v.*y和v.*?y #so very very vary vary sorry vys v.*y #贪婪 so =very very vary vary sorry vy=s v.*?y #非贪婪so =very very vary vary sorry= =vy=s v.+?y #so =very very vary vary= sorry vys v.??y #so very very vary vary sorry =vy=s =vay= v.{2,}?y #so =very very vary vary= sorry =vys varafday=
如果要使用非贪婪,则加一个 ? ,上面的例子修改如下:
import re a = 'java*&39android##@@python' # 贪婪与非贪婪 re_findall = re.findall('[a-z]{4,7}?', a) print(re_findall)
输出结果如下:
['java', 'andr', 'pyth']
引擎选项
IgnoreCase 匹配时忽略大小写。如re.I re.IGNORECASE Singleline 单行模式,即整个测试文本被认为是单行。可以匹配所有字符,包括\n。如re.S re.DOTALL Multiline 多行模式,^行首、$行尾。如re.M re.MULTLINE IgnorePatternWhitespace 忽略表达式中的空白字符,如果要使用空白字符用转义,#可以用来做注释。 如re.X re.VERBOSE
- 默认模式:将整个测试字符串看做一个一行的大字符串,.不能代表\n
- 单行模式:将整个测试字符串看做一个一行的大字符串,.可以匹配\n
- 多行模式:终于可以把一行大字符串用\n分割成多行,^指的是行首,$行尾,.行为不变
单行模式: . 可以匹配所有字符,包括换行符 ^ 表示整个字符串的开头,$整个字符串的结尾 多行模式 . 可以匹配除了换行符之外的字符,多行不影响.点号 ^ 表示行首,$行尾,只不过这里的行是每一行 默认模式:可以看做待匹配的文本是一行,不能看做多行,.点号不能匹配换行符,^和$表示行首和行尾,而行首行尾就是整个字符的开头和结尾 单行模式:基本和默认模式一样,只是.点号终于可以匹配任意一个字符包括换行符,这时所有文本就是一个长长的只有一行的字符串。^就是这一行字符串的行首,$就是这一行的行尾。 多行模式:重新定义了行的概念,但不影响.点号的行为,^和$还是行首行尾的意思,只不过因为多行模式可以识别换行符了。“开始”指的是\n后紧接着下一个字符;“结束”指的是\n前的字符,注意最后一行结尾可以没有\n 简单讲,单行模式只影响.点号行为,多行模式重新定义行影响了^和$ 注意:字符串看不见的换行符,\r\n会影响e$的测试,e$只能匹配e\n
范例
so very very sorry vys ray v.*y 匹配,整个看做一行,从very 到sorry,遇到\n不能突破,后面再匹配vys ray so =very very sorry= =vys ray= v.*y 单行模式匹配,可以突破\n =very very sooryvys ray= ^v.*y 单行模式匹配。 空 ^v.*y 默认匹配。 空 v.*y 多行模式匹配 so =very very sorry= =vys ray= ^v.*y 多行模式匹配 =vys ray= ^v.*y 单行和多行模式匹配 =vys ray= ^v.*y$ 单行模式匹配。 空
边界匹配符和组
将上面几个点,就用了很大的篇幅了,现在介绍一些边界匹配符和组的概念。
一般的边界匹配符有以下几个:
语法 描述 ^ 匹配字符串开头(在有多行的情况中匹配每行的开头) $ 匹配字符串的末尾(在有多行的情况中匹配每行的末尾) \A 仅匹配字符串开头 \Z 仅匹配字符串末尾 \b 匹配 \w 和 \W 之间 \B [^\b]
分组,被括号括起来的表达式就是分组。分组表达式 (…) 其实就是把这部分字符作为一个整体,当然,可以有多分组的情况,每遇到一个分组,编号就会加 1 ,而且分组后面也是可以加数量词的。
re.sub
实战过程中,我们很多时候需要替换字符串中的字符,这时候就可以用到 def sub(pattern, repl, string, count=0, flags=0) 函数了,re.sub 共有五个参数。其中三个必选参数:pattern, repl, string ; 两个可选参数:count, flags .
具体参数意义如下:
参数 描述 pattern 表示正则中的模式字符串 repl repl,就是replacement,被替换的字符串的意思 string 即表示要被处理,要被替换的那个 string 字符串 count 对于pattern中匹配到的结果,count可以控制对前几个group进行替换 flags 正则表达式修饰符
具体使用可以看下下面的这个实例,注释都写的很清楚的了,主要是注意一下,第二个参数是可以传递一个函数的,这也是这个方法的强大之处,例如例子里面的函数 convert ,对传递进来要替换的字符进行判断,替换成不同的字符。
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import re a = 'Python*Android*Java-888' # 把字符串中的 * 字符替换成 & 字符 sub1 = re.sub('\*', '&', a) print(sub1) # 把字符串中的第一个 * 字符替换成 & 字符 sub2 = re.sub('\*', '&', a, 1) print(sub2) # 把字符串中的 * 字符替换成 & 字符,把字符 - 换成 | # 1、先定义一个函数 def convert(value): group = value.group() if (group == '*'): return '&' elif (group == '-'): return '|' # 第二个参数,要替换的字符可以为一个函数 sub3 = re.sub('[\*-]', convert, a) print(sub3)
输出的结果:
Python&Android&Java-888 Python&Android*Java-888 Python&Android&Java|888
re.match 和 re.search
re.match 函数
语法:
re.match(pattern, string, flags=0)
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 none。
a = 'Python*Android*Java-888' # 使用 re.match match = re.match('[a-zA-Z]{4,7}', a) # group(0) 是一个完整的分组 print(match.group(0)) #Python # 限制匹配长度为 7 match = re.match('[a-zA-Z]{7}', a) print(match.group(0)) #报错,没有从起始位置7位的字符串
re.search 函数
语法:
re.search(pattern, string, flags=0)
re.search 扫描整个字符串并返回第一个成功的匹配。 re.match 和 re.search 的参数,基本一致的,具体描述如下:
参数 描述 pattern 匹配的正则表达式 string 要匹配的字符串 flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写
那么它们之间有什么区别呢?
re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None;而 re.search匹配整个字符串,直到找到一个匹配。这就是它们之间的区别了。
可能在个人的使用中,可能更多的还是使用 re.findall
看下下面的实例,可以对比下 re.search 和 re.findall 的区别,还有多分组的使用。具体看下注释,对比一下输出的结果:
示例:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- # 提取图片的地址 import re a = '<img src="https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg">' # 使用 re.search search = re.search('<img src="(.*)">', a) # group(0) 是一个完整的分组 print(search.group(0)) print(search.group(1)) # 使用 re.findall findall = re.findall('<img src="(.*)">', a) print(findall) # 多个分组的使用(比如我们需要提取 img 字段和图片地址字段) re_search = re.search('<(.*) src="(.*)">', a) # 打印 img print(re_search.group(1)) # 打印图片地址 print(re_search.group(2)) # 打印 img 和图片地址,以元祖的形式 print(re_search.group(1, 2)) #('img', 'https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg') # 或者使用 groups print(re_search.groups())
输出的结果:
<img src="https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg"> https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg ['https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg'] img https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg ('img', 'https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg') ('img', 'https://www.cici.com/a8c49ef606e0e1f3ee28932usk89105e.jpg')
正则习题
IP地址
匹配合法的IP地址
192.168.1.150 0.0.0.0 255.255.255.255 17.16.52.100 172.16.0.100 400.400.999.888 001.022.003.000 257.257.255.256
提取文件名
选出含有ftp的链接,且文件类型是gz或者xz的文件名
ftp://ftp.astron.com/pub/file/file-5.14.tar.gz ftp://ftp.gmplib.org/pub/gmp-5.1.2/gmp-5.1.2.tar.xz ftp://ftp.vim.org/pub/vim/unix/vim-7.3.tar.bz2 http://anduin.linuxfromscratch.org/sources/LFS/lfs-packages/conglomeration//iana-etc/iana-etc-2.30.tar.bz2 http://anduin.linuxfromscratch.org/sources/other/udev-lfs-205-1.tar.bz2 http://download.savannah.gnu.org/releases/libpipeline/libpipeline-1.2.4.tar.gz http://download.savannah.gnu.org/releases/man-db/man-db-2.6.5.tar.xz http://download.savannah.gnu.org/releases/sysvinit/sysvinit-2.88dsf.tar.bz2 http://ftp.altlinux.org/pub/people/legion/kbd/kbd-1.15.5.tar.gz http://mirror.hust.edu.cn/gnu/autoconf/autoconf-2.69.tar.xz http://mirror.hust.edu.cn/gnu/automake/automake-1.14.tar.xz
匹配邮箱地址
test@hot-mail.com [email protected] [email protected] [email protected] a@w-a-com
匹配html标记
提取href中的链接url,提取文字“CiCi”
<a href='http://www.cici.com/index.html' target='_blank'>CiCi</a>
匹配URL
http://www.cici.com/index.html https://login.cici.com file:///ect/sysconfig/network
匹配二代中国身份证ID
321105700101003 321105197001010030 11210020170101054X 17位数字+1位校验码组成 前6位地址码,8位出生年月,3位数字,1位校验位(0-9或X)
路径操作
路径操作模块
# coding: utf-8 #os 模块常用函数 os.path模块 from os import path p1 = path.join('d:/tmp', 'conf', 'a/b') #拼接 print(p1, type(p1)) #b basename基名, d:/tmp/conf/a 路径名dirname print(p1.split('/')) #str.split print(path.split(p1)) #p1:str 一刀, replit, sep \ / #: d:/tmp\conf\a/b <class 'str'> #: ['d:', 'tmp\\conf\\a', 'b'] #: ('d:/tmp\\conf\\a', 'b') d, b = path.split(p1) print(d, b) #: d:/tmp\conf\a b print(path.split(d)) #: ('d:/tmp\\conf', 'a') print(d, path.dirname(d), path.basename(d)) #: d:/tmp\conf\a d:/tmp\conf a print(path.exists('d:/tmp')) #存在 print(path.exists('d:/tmp/test')) print(path.splitdrive('d:/tmp/test')) #仅windows,区分驱动器 print(path.abspath('d:/tmp/test')) #对应的绝对路径 print(__file__) #当前文件自己的路径 print(path.isdir('d:/')) # d盘的/是目录吗 print(path.isfile('d:/tmp/test')) # 是文件吗 #: True #: True #: ('d:', '/tmp/test') #d:\tmp\test #<stdin> #True #True #打印父目录 p = 'd:/tmp/test' while p != path.dirname(p): p = path.dirname(p) print(p) #d:/tmp #d:/
3.4版本开始,建议使用pathlib模块,提供Path对象来操作。包括目录和文件。
Path类
从3.4开始python提供了pathlib模块,使用path类操作目录更加方便。
# coding: utf-8 from pathlib import Path #内部调用的os.path #获取当前路径对象 print(Path(), Path(''), Path('.')) #会检查当前操作系统 WindowsPath('.') print(Path().absolute()) #绝对路径 WindowsPath('D:/project/pyprojs') import os print(os.path.abspath(''), os.path.abspath('.')) #绝对路径,相比Path类使用复杂些 #字符串显现方法,路径拼接 p = Path('/etc/', 'sysconfig', 'c/d') print(p) #自动调整。windows 下: \etc\sysconfig\c\d print(str(p), repr(p), bytes(p)) #p 内部调用的是repr : \etc\sysconfig\c\d WindowsPath('/etc/sysconfig/c/d') b'\\etc\\sysconfig\\c\\d' #bytes(p) == str(p).encode() print(Path('.', 'a', 'b/c', '', Path(''), Path('e/f'))) #: a\b\c\e\f print(Path('a') / 'b/c') # / 运算符重载 #: a\b\c print(Path('a') / 'b/c' / '' / Path('d')) #: a\b\c\d print('a/b' / Path('c', 'd', Path('e/f'))) #: a\b\c\d\e\f #print('a/b' / 'c/d' / Path('e/f')) # 报错,字符不能相除 print('a/b' / ('c/d' / Path('e/f'))) #a\b\c\d\e\f #str / Path => Path #Path / Path => Path #Path / str => Path #str / str 错的 # 由哪些部分组成 p = ('a/b' / ('c/d' / Path('e/f'))) print(p.parts) #('a', 'b', 'c', 'd', 'e', 'f') p1 = ('/a/b' / ('c/d' / Path('e/f'))) print(p1.parts) #绝对路径,多了根 ('\\', 'a', 'b', 'c', 'd', 'e', 'f') print(p1.parent) #取父路径 \a\b\c\d\e print(p1.parent.parent.parent.parent.parent.parent) #取父路径 \ print(p1.parent.joinpath('d1', 'd2/d3', Path('d4', 'd5'))) #路径拼接 \a\b\c\d\e\d1\d2\d3\d4\d5 l = p1.parents #惰性对象 print(list(l), p1) #父路径,是由近及远 #[WindowsPath('/a/b/c/d/e'), WindowsPath('/a/b/c/d'), WindowsPath('/a/b/c'), WindowsPath('/a/b'), WindowsPath('/a'), WindowsPath('/')] \a\b\c\d\e\f
初始化
# coding: utf-8 from pathlib import Path p = Path() #当前目录,Path() Path('.') Path('') p = Path('a', 'b', 'c/d') #当前目录下的 a/b/c/d p = Path('/etc', Path('sysconfig'), 'network/ifcfg') #根下的etc目录
路径拼接
操作符 /
- Path对象 / Path对象
- Path对象 / Path对象
- 字符串 / Path对象
joinpath
- joinpath(*other) 在当前Path路径上连接多个字符串返回新路径对象
# coding: utf-8 from pathlib import Path p1 = ('/a/b' / ('c/d' / Path('e/f'))) print(p1.parent) #取父路径 \a\b\c\d\e print(p1.parent.joinpath('d1', 'd2/d3', Path('d4', 'd5'))) #路径拼接 \a\b\c\d\e\d1\d2\d3\d4\d5
分解
parts 属性,会返回目录各部分的元组
# coding: utf-8 from pathlib import Path p = Path('/a/b/c/d') print(p.parts) #最左边的是/是根目录 #: ('\\', 'a', 'b', 'c', 'd')
获取路径
str 获取路径字符串
bytes 获取路径字符串的 bytes
from pathlib import Path p = Path('/etc') print(str(p), bytes(p)) #: \etc b'\\etc'
父目录
parent 目录的逻辑父目录
parents 父目录惰性可迭代对象,索引0是直接的父
# coding: utf-8 from pathlib import Path p = Path('/data/mysql/install/my.tar.gz') print(p.parent) #: \data\mysql\install for x in p.parents: #可迭代对象 print(x) #: \data\mysql\install #: \data\mysql #: \data #: \
目录组成部分
name、stem、suffix、suffixes、with_suffix(suffix)、with_name(name)
- name 目录的最后一个部分
- stem 目录最后一个部分,没有后缀
- suffix 目录最后一个部分的扩展名
- name = stem + suffix
suffixes 返回多个扩展名列表
- with_suffix(suffix) 有扩展名则替换,无则补充扩展名
- with_name(name)替换目录最后一个部分并返回一个新的路径
# coding: utf-8 from pathlib import Path p = Path('/data/mysql/my.tar.gz') print(p.exists()) #: False print(p.name, p.parent) #路径成分 基名 父目录 : my.tar.gz \data\mysql print(p.suffix, p.suffixes, p.stem) #文件扩展名 : .gz ['.tar', '.gz'] my.tar print(p.with_name('config')) #换基名 : \data\mysql\config print(p.parent.with_name('config')) #换基名 : \data\config print(p.parent.parent / 'redis') #换基名: \data\redis print(p.with_suffix('.xz')) #换扩展名 : \data\mysql\my.tar.xz
全局方法
- cwd() 返回当前工作目录
- home() 返回当前家目录
# coding: utf-8 from pathlib import Path p = 'd:/tmp' print(Path(p).cwd(), Path.cwd()) # 当前工作路径不会随着你路径的变化而变化,相当于常量,通过任何路径都可以方便调用到当前工作路径 #家目录也是一样,相当于常量 print(Path('abc').home(), Path.home())
判断方法
- exists() 目录或文件是否存在
- is_dir() 是否是目录,目录存在返回True
- is_file() 是否是普通文件,文件存在返回True
- is_symlink() 是否是软链接
- is_socket() 是否是socket文件
- is_block_device() 是否是块设备
- is_char_device() 是否是字符设备
- is_absolue() 是否是绝对路径
注意: 文件只有存在 ,才能知道它是什么类型文件
绝对路径
- resolve() 非windows,返回一个新的路径,这个新路径就是当前Path对象的绝对路径。如果是软链接则直接被解析
- absolute() 获取绝对路径
其它操作
- rmdir() 删除空目录。没有提供判断目录为的方法
- touch(mode=0o666, exist_ok=True) 创建一个文件
- as_uri() 将路径返回成URI,例如'file:///etc/passwd'
mkdir(mode=0o777, parents=False, exist_ok=False)
parents 是否创建父目录,True等同于mkdir -p。False时,父目录不存在,则抛出FileExistsError被忽略
exist_ok 在3.5版本加入。False时,路径存在,抛出FileExistsError; True时 FileExistsError被忽略
- iterdir() 迭代当前目录,不递归
# coding: utf-8 from pathlib import Path p = Path('d:/tmp/') print(p.is_file()) print(p.exists()) print(list(p.absolute().iterdir())) #遍历目录,iterdir方法遍历,返回生成器对象,遍历目录不递归 (p / 'd1').mkdir() #创建目录 (p / 'c1/c2/c3').mkdir(parents=True) #创建目录,mkdir -p (p / 'c1/a.py').touch() #创建文件 (p / '../a.py').touch() #创建文件 #遍历父路径 for x in (p / 'd1').parent.iterdir(): print(x) #d:\tmp\10.mp3 #d:\tmp\a.png #d:\tmp\c01-c基础.mht #d:\tmp\c1 #d:\tmp\chat-0.jpg #d:\tmp\config-zy2.json #d:\tmp\d1
范例:d:/tmp下的所有文件,不递归,判断是否为空目录,判断文件类型
# coding: utf-8 from pathlib import Path p = Path('d:/tmp/c1') print(list(p.parents)) for x in p.parents[len(p.parents)-2].iterdir(): print(x, type(x)) if x.is_dir(): print('dir', x) # flag = False #不为空 # for z in x.iterdir(): # flag = True # break # print('Not empty' if flag else 'Empty') print('Not empty' if next(x.iterdir(), False) else 'Empty') elif x.is_file(): print('file', x) else: print('other', x)
- stat 相当于stat -L命令,跟踪软链接
- lstat 使用方法同stat(),但如果是符号链接,则显示符号链接本身的文件信息。
通配符
- glob(pattern) 通配给定的模式,返回生成器对象
- rglob(pattern) 通配给定的模式,递归目录,返回生成器对象
- ? 代表一个字符
- * 表示任意个字符
- [abc]或[a-z]表示一个字符
# coding: utf-8 from pathlib import Path #通配符,不是正则表达式 p1 = Path('d:/tmp') print(p1.glob('*')) #当前目录下所有文件,返回可迭代对象: <generator object Path.glob at 0x0000027452864120> print(p1.glob('*.py')) #当前目录下所有.py文件 print(p1.glob('*/*/*.py')) #当前目录下子目录下所有.py文件 print(p1.glob('**/*.py')) #当前目录下任意目录下所有.py文件 #? 一个 p1.glob('**/[a-z]*.??') #[a-z]*.?? 代表a到z任意一个字符后面跟任何字符,再接个.?? p1.rglob('**/[a-z]*.??') #递归glob,层级就不用写了
文件操作
Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
使用方法类似内建函数open。返回一个文件对象
3.5增加了新函数
Path.read_bytes()
以 'rb' 读取路径对应文件,并返回二进制流。看源码
Path.read_text(encoding=None, errors=None)
以'rt'方式读取路径对应文件,返回文本
Path.write_bytes(date)
以'wb'方式写入数据到路径对应文本
Path.write_text(date, encoding=None, errors=None)
以'wt'方式写入字符串到路径对应文件
open()函数文件操作
# coding: utf-8 from pathlib import Path p = Path('d:/tmp/a.py') with open(p, 'w+') as f: #open文件参数可以是 Path对象、字符串、文件对象 f.write('abc') f.seek(0) print(f.read())
Path类中的文件操作
# coding: utf-8 from pathlib import Path p = Path('d:/tmp/a.py') p.write_text('abc') #内部用了open(mode='w') p.write_bytes(b'abc') #内部用了open(mode='wb') print(p.read_text()) #open(mode='rt') print(p.read_bytes()) #open(mode='rb') #: abc #: b'abc' with p.open() as f: #self.open(mode, ...) open(file, mode) print(f.read())
os模块
操作系统平台
os.name
windows是nt,linux是posixos.uname()
*nix支持sys.platform
windows显示win32, linux显示linux
os.listdir('d:/tmp') 返回指定目录内容列表,不递归
os也有open、read、write等方法,但是太底层,建议使用内建函数open、read、write,使用方式相似
os.stat(path, *, dir_fd=None, follow_symlinks=True)
本质上调用linux系统的stat
# coding: utf-8 import os import sys print(os.name, sys.platform) #: nt win32 print(os.listdir('d:/tmp')) #就不递归 print(os.path.isdir('d:/tmp')) print(os.stat('d:/tmp/test')) #mode=33188 print(os.lstat('d:/tmp/test')) print(oct(31888)) #八进制 0o76220 权限为rwww
os.chmod(path, mode, *, dir_fd=None, follow_symlinks=True)
os.chmod('test', 0o777)
os.chown(path, uid, git)
改变文件的属性、属组,但需要足够的权限
shutil模块
文件拷贝:使用打开2文件对象,源文件读取内容,写入目标文件中来完成拷贝过程。但是这样丢失stat数据信息(权限等),因为根本没有复制这些信息过去。
目录复制又怎么办呢?
Python提供了一个方便的库shutil(高级文件操作)
copy 复制
copyfileobj(fsrc, fdst[, length])
文件对象的复制,fsrc和fdst是open打开的文件对象,复制内容。fdst要求可写。
length指定了表示buffer的大小
阅读copyfileobj函数。
copyfile(src, dst, *, follow_symlinks=True)
复制文件内容,不含元数据。src、dst为文件的路径字符串。
本质上调用的就是copyfileobj,所以不带元数据二进制内容复制。
copymode(src, dst, *, follow_symlinks=True)
仅复制权限
copystat(src, dst, *, follow_symlinks=True)
复制文件权限和元数据
copy(src, dst, *, follow_symlinks=True)
内部用了copyfile() 和 copymode()
copy2(src, dst, *, follow_symlinks=True)
内部用了copyfile() 和 copystat()
# coding: utf-8 from pathlib import Path import shutil src = Path('d:/tmp/test') dst = Path('d:/tmp/t.txt') with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: #从src读取,写入dst shutil.copyfileobj(fsrc, fdst) #shutil.copyfile() #shutil.copymode() #shutil.copystat() #stat 全面元数据,包括mode #shutil.copy() #copyfile copymode #shutil.copy2() #copyfile copystat
copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False)
递归复制目录。默认使用copy2。src, dst必须是目录,src必须存在,dst必须不存在。ignore = func ,返回被过滤文件名的set类型数据。
# coding: utf-8 from pathlib import Path import shutil src = Path('d:/tmp/d1') #递归copy d1 子文件 dst = Path('d:/tmp/dx') #shutil.copytree(src, dst) #源目录递归copy到dst,要求目标路径不能存在 def fn(src, files): #src, name表示src所有直接的文件 # print(src, files) #d:/tmp/d1 ['b.txt', 'd2'] # ignore_names = set() #集合解析式 # for name in names: #不要copy .py文件 #文件名字符串列表 # print(name, type(name)) # if name.endswith('.py') or name.endswith('.txt'): # ignore_names.add(name) # return ignore_names #返回可迭代对象 #return { name for name in names if name.endswith('.py') or name.endswith('.txt')} return set(filter(lambda name: name.endswith('.py') or name.endswith('.txt'), names)) #可迭代对象 shutil.rmtree('d:/tmp/dx', ignore_errors=True) shutil.copytree(src, dst, ignore=fn) #源目录递归copy到dst,要求目标路径不能存在
rm 删除
shutil.rmtree(path, ignore_errors=False, onerror=None)
递归删除。如同rm -fr一样危险,慎用。
move 移动
move(src, dst, copy_function=copy)
递归移动文件、目录到目标,返回目标。
本身使用的是 os.rename 方法。
shutil.move('d:/a', 'd:/aaa') os.rename('d:/t.txt', 'd:/tmp/t') os.rename('test3', 'd:/tmp/taa')
shutil还有打包功能。生成tar并压缩。支持zip, gz, bz, xz。
练习
指定一个源文件,实现copy到目标目录
例如把/tmp/test.txt拷贝到/tmp/test1.txt
单词统计
有一个文件,对其进行单词统计,不区分大小写,并显示单词重复最多的10个单词
单词统计进阶
在上一题的基础上,要求用户可以排除一些单词的统计,例如a, the, of等不应该出现在具有实际意义的统计中,应当忽略。要求全部代码使用函数封装,并调用完成
配置文件转换
有一个配置文件test.ini内容如下,将其转换成json格式文件
[DEFAULT] a = test [mysql] default-character-set=utf8 a = 100 [mysqld] datadir =/dbserver/data port = 33060 character-set-server=utf8 sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
复制目录
选择一个已存在的目录作为当前工作目录,在其下创建a/b/c/d这样的子目录结构并在这些子目录的不同层级生成50个普通文件,要求文件名由随机4个小写字母构成。
将a目录下所有内容复制到当前工作目录dst目录下,要求复制的普通文件的文件名必须是x, y, z开头。
举例,假设工作目录/tmp,构建的目录结构是/tmp/a/b/c/d。在a, b, c, d目录中放入随机生成的文件,这些文件的名称也是随机生成的。最终把a目录下所有的目录也就是b, c, d目录,和文件名开头是x, y, z开头的文件。
指定一个源文件,实现copy到目标目录
例如把/tmp/test.txt拷贝到/tmp/test1.txt
单词统计
有一个文件,对其进行单词统计,不区分大小写,并显示单词重复最多的10个单词
单词统计进阶
在上一题的基础上,要求用户可以排除一些单词的统计,例如a, the, of等不应该出现在具有实际意义的统计中,应当忽略。要求全部代码使用函数封装,并调用完成
配置文件转换
有一个配置文件test.ini内容如下,将其转换成json格式文件
[DEFAULT] a = test [mysql] default-character-set=utf8 a = 100 [mysqld] datadir =/dbserver/data port = 33060 character-set-server=utf8 sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
复制目录
选择一个已存在的目录作为当前工作目录,在其下创建a/b/c/d这样的子目录结构并在这些子目录的不同层级生成50个普通文件,要求文件名由随机4个小写字母构成。
将a目录下所有内容复制到当前工作目录dst目录下,要求复制的普通文件的文件名必须是x, y, z开头。
举例,假设工作目录/tmp,构建的目录结构是/tmp/a/b/c/d。在a, b, c, d目录中放入随机生成的文件,这些文件的名称也是随机生成的。最终把a目录下所有的目录也就是b, c, d目录,和文件名开头是x, y, z开头的文件。