文件操作
冯诺依曼体系架构
CPU由运算器和控制器组成
运算器,完成各种算数运算、逻辑运算、数据传输等数据加工处理
控制器,控制程序的执行
存储器,用于记忆程序和数据,例如内存
输入设备,将数据或者程序输入到计算机中,例如键盘、鼠标
输出设备,将数据或程序的处理结果展示给用户,例如显示器、打印机等
一般说IO操作,指的是文件IO,如果指的是网络IO,都会直接说网络IO
文件IO常用操作
命令
解释
open
打开
read
读取
write
写入
close
关闭
readline
行读取
readlines
多行读取
seek
文件指针操作
tell
指针位置
打开操作 open(file,mode='r',buffering=-1,encoding=None,errors=None,newline=None,closefd=True,opener=None)
打开一个文件,返回一个文件对象(流对象)和文件描述符。打开文件失败,则返回异常
基本使用:
创建一个文件test,然后打开它,用完关闭
1 2 3 4 5 6 f = open ("test" ) print (f.read()) f.close()
文件操作中,最常用的操作就是读和写。
文件访问的模式有两种:文本模式和二进制模式。不同模式下,操作函数不尽相同,表现的结果也不一样。
open的参数 file 打开或者要创建的文件名。如果不指定路径,默认是当前路径
mode模式
描述字符
意义
r
缺省的,表示只读打开
w
只写打开
x
创建并写入一个新文件
a
写入打开,如果文件存在,则追加
b
二进制模式
t
缺省的,文本模式
+
读写打开一个文件。给原来只读、只写方式打开提供缺失的读或者写能力
在上面的例子中,可以看到默认是文本打开模式,且是只读的。
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 f = open ('test' ) f.read() f.write('abc' ) f.close() f = open ('test' ,'r' ) f.write('abc' ) f.close() f = open ('test1' ,'r' ) f = open ('test' ,'w' ) f.write('abc' ) f.close() >>> cat test f = open ('test' ,mode='w' ) f.close() >>> cat test f = open ('test1' ,mode='w' ) f.write('123' ) f.close() >>> cat test1
open默认是以只读模式r打开已经存在的文件。
r
只读打开文件,如果使用write方法,会抛异常。
如果文件不存在,抛出FileNotFoundError异常
w
表示只写方式打开,如果读取则抛出异常
如果文件不存在,则直接创建文件
如果文件存在,则清空文件内容
1 2 3 4 5 6 f = open ('test2' ,'x' ) f.write('abcd' ) f.close()
x
文件不存在,创建文件,并以只写方式打开
文件存在,抛出FileExistsError异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 f = open ('test2' ,'a' ) f.read() f.write('abcde' ) f.close() >>> cat test2f = open ('test2' ,'a' ) f.write('\n hello' ) f.close() >>> cat test2f = open ('test3' ,'a' ) f.write('test3' ) f.close() >>> cat test3
a
文件存在,只写打开,追加内容
文件不存在,则创建后,只写打开,追加内容
r是只读,wxa都是只写。
wxa都可以产生新文件,w不管文件存在与否,都会生成全新内容的文件;a不管文件是否存在,都能在打开的文件尾部追加;x必须要求文件事先不存在,自己造一个新文件
文本模式t
字符流,将文件的字节按照某种字符编码理解,按照字符操作。open的默认mode就是rt。
二进制模式b
字节流,将文件就按照字节理解,与字符编码无关。二进制模式操作时,字节操作使用bytes类型
1 2 3 4 5 6 7 8 9 10 11 12 f = open ("test3" ,'rb' ) s = f.read() print (type (s)) print (s) f.close() f = open ("test3" ,'wb' ) s = f.write("马哥教育" .encode()) print (s) f.close() cat test3
思考:windows下,执行下面的代码
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 f = open ("test3" ,'rw' ) f = open ("test3" ,'r+' ) s = f.read() f.write("马哥教育" ) print (f.read()) f.close() f = open ("test3" ,'r+' ) s = f.write("magedu" ) print (f.read())f.close() >>> cat test3f = open ('test3' ,'w+' ) r.read() f.close() >>> cat test3f = open ('test3' ,'a+' ) f.write('mag' ) f.read() f.close() >>> cat test3f = open ('test3' ,'a+' ) f.write('edu' ) f.close() >>> cat test3f = open ('test3' ,'x+' ) f = open ('test4' ,'x+' ) f.write('python' ) f.read() f.close() >>> cat test4
为r、w、a、x提供缺失的读写功能,但是,获取文件对象依旧按照r、w、a、x自己的特征。
+不能单独使用,可以认为它是为前面的模式字符做增强功能的。
文件指针 上面的例子中,已经说明了有一个指针。
文件指针,指向当前字节位置
mode=r,指针起始在0
mode=a,指针起始在EOF
tell()
显示指针当前位置
seek(offset[,whence])
移动文件指针位置。offset偏移多少字节,whence从哪里开始。
文件模式下
whence 0 缺省值,表示从头开始,offset只能正整数
whence 1 表示从当前位置,offset只接受0
whence 2表示从EOF开始,offset只接受0
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 f = open ('test4' ,'r+' ) f.tell() f.read() f.tell() f.seek(0 ) f.read() f.seek(2 ,0 ) f.read() f.seek(2 ,0 ) f.seek(2 ,1 ) f.seek(2 ,2 ) f.close() f = open ('test4' ,'w+' ) f.write('马哥教育' ) f.tell() f.close() f = open ('test4' ,'r+' ) f.read(3 ) f.seek(1 ) f.tell() f.read() f.seek(2 ) f.close()
文本模式支持从开头向后偏移的方式。
whence为1表示从当前位置开始偏移,但是只支持偏移0,相当于原地不动,所以没什么用。
whence为2表示从EOF开始,只支持偏移0,相当于移动文件指针到EOF。
seek是按照字节偏移的。
二进制模式下
whence 0 缺省值,表示从头开始,offset只能正整数
whence 1 表示从当前位置,offset可正可负
whence 2 表示从EOF开始,offset可正可负
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 f = open ('test' ,'rb+' ) f.tell() f.read() f.tell() f.write(b'abc' ) f.seek(0 ) f.seek(2 ,1 ) f.read() f.seek(-2 ,2 ) f.seek(2 ,2 ) f.seek(0 ) f.seek(-2 ,2 ) f.read() f.seek(-20 ,2 ) f.close()
二进制模式支持任意起点的偏移,从头、从尾、从中间位置开始。
向后seek可以超界,但是向前seek的时候,不能超界,否则抛异常。
buffering:缓冲区 -1 表示使用缺省大小的buffer。如果是二进制模式,使用io.DEFAULT_BUFFER_SIZE值,默认是4096或者8192。如果是文本模式,如果是终端设备,是行缓存方式,如果不是,则使用二进制模式的策略。
0 只在二进制模式使用,表示关buffer
1 只在文本模式使用,表示使用行缓冲。意思就是见到换行符就flush
大于1用于指定buffer的大小
buffer缓冲区
缓冲区一个内存空间,一般来说是一个FIFO队列,到缓冲区满了或者达到阈值,数据才会flush到磁盘。
flush()
将缓冲区数据写入磁盘
close()
关闭前会调用flush()
io.DEFAULT_BUFFER_SIZE
缺省缓冲区大小,字节
二进制模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import iof = open ('test4' ,'w+b' ) print (io.DEFAULT_BUFFER_SIZE)f.write("magedu.com" .encode()) f.seek(0 ) f.write("www.magedu.com" .encode()) f.flush() f.close() f = open ('test4' ,'w+b' ,4 ) f.write(b"mag" ) f.write(b'edu' ) f.close()
文本模式
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 f = open ('test4' ,'w+' ,1 ) f.write("mag" ) f.write("magedu" *4 ) f.write('\n' ) f.write('Hello\nPython' ) f.close() f = open ('test4' ,'w+' ,15 ) f.write("mag" ) f.write('edu' ) f.write('Hello\n' ) f.write('\nPython' ) f.write('a' * (io.DEFAULT_BUFFER_SIZE - 20 )) f.write('\nwww.magedu.com/python' ) f.close() f = open ('test4' ,'wb+' ,0 ) f.write(b"m" ) f.write(b"a" ) f.write(b"g" ) f.write(b"magedu" *4 ) f.write(b'\n' ) f.write(b'Hello\nPython' ) f.close()
buffering
说明
buffering=-1
t和b,都是io.DEFAULT_BUFFER_SIZE
buffering=0
b:关闭缓冲区;t:不支持
buffering=1
b:就1个字节;t:行缓冲,遇到换行符才flush
buffering>1
b模式表示行缓冲大小。缓冲区的值可以超过io.DEFAULT_BUFFER_SIZE,直到设定的值超出后才把缓冲区flush;t模式,是io.DEFAULT_BUFFER_SIZE,flush完后把当前字符串也写入磁盘
似乎看起来很麻烦,一般来说,只需要记得:
文本模式,一般都用默认缓冲区大小
二进制模式,是一个个字节的操作,可以指定buffer的大小
一般来说,默认缓冲区大小是个比较好的选择,除非明确知道,否则不调整它
一般编程中,明确知道需要写磁盘了,都会手动调用一次flush,而不是等到自动flush或者close的时候
encoding:编码,仅文本模式使用 None表示使用缺省编码,依赖操作系统。windows、linux下测试如下代码
1 2 3 f = open ('test1' ,'w' ) f.write('啊' ) f.close()
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'
会被替换为指定的字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 f = open ('o:/test' ,'w' ) f.write('python\rwww.python.org\nwww.magedu.com\r\npython3' ) f.close() newlines = [None ,'' ,'\n' ,'\r\n' ] for n1 in newlines: f = open ('o:/test' ,'r+' ,newline=n1) print (f.readlines()) f.close()
closefd 关闭文件描述符,True表示关闭它。False会在文件关闭后保持这个描述符。fileobj.fileno()
查看
read read(size=-1)
,size表示读取的多少个字符或字节;负数或者None表示读取到EOF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 f = open ('o:/test4' ,'r+' ,0 ) f.write("magedu" ) f.write('\n' ) f.write('马哥教育' ) f.seek(0 ) f.read(7 ) f.close() f = open ('test4' ,'rb+' ) f.read(7 ) f.read(1 ) f.close()
行读取 readline(size=-1)
一行行读取文件内容。size设置一次能读取行内几个字符或字节
readlines(hint=-1)
读取所有行的列表。指定hint则返回指定的行数。
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 f = open ('test' ) for line in f: print (line) f.close() f = open ('test' ) f.readlines() f.close() f = open ('test' ) f.readline()
write write(s),把字符串s写入到文件中并返回字符的个数
writelines(lines)
,将字符串列表写入文件
1 2 3 4 5 6 7 8 9 10 11 12 f = open ('test' ,'w+' ) lines = ['abc' ,'123\n' ,'magedu' ] f.writelines(lines) f.seek(0 ) print (f.read())f.close()
close flush并关闭文件对象。文件已经关闭,再次关闭没有任何效果。
其他 seekable()
是否可seek
readable()
是否可读
writable()
是否可写
closed
是否已经关闭
上下文管理 问题的引出 在linux中,执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 lst = [] for _ in range (2000 ): lst.append(open ('test' )) print (len (lst))lsof 列出打开的文件。没有就用yum install lsof lsof -p 1427 | grep test | wc -l lsof -p 进程号 ulimit -a 查看所有限制。其中open files就是打开文件数的限制,默认1024 for x in lst: x.close()
有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。
如何解决?
异常处理
当出现异常的时候,拦截异常。但是,因为很多代码都可能出现OSError异常,还不好判断异常就是因为资源限制产生的
1 2 3 4 5 6 7 8 f = open ('test' ) try : f.write("abc" ) finally : f.close()
上下文管理
一种特殊的语法,交给解释器去释放文件对象
with工作原理
基本思想是with所求值的对象必须有一个__enter__()
方法,一个__exit__()
方法。
紧跟with后面的语句被求值后,返回对象的__enter__()
方法被调用,这个方法的返回值将被赋值给as后面的变量;
当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()
方法。
1 2 3 4 5 6 7 8 9 10 11 del fwith open ('test' ) as f: f.write("abc" ) f.closed With open ('1.txt' ) as f1, open ('2.txt' ) as f2: do something
上下文管理
使用with … as 关键字
上下文管理的语句块并不会开启新的作用域
with 语句块执行完的时候,会自动关闭文件对象
另一种写法
1 2 3 4 5 6 f1 = open ('test' ) with f1: f1.write("abc" ) f1.closed
对于类似于文件对象的IO对象,一般来说都需要在不使用的时候关闭、注销、以释放资源。IO被打开的时候,会获得一个文件描述符。计算机资源是有限的,所以操作系统都会做限制。就是为了保护计算机的资源不要被完全耗尽,计算资源是共享的,不是独占的。一般情况下,除非特别明确的知道资源情况,否则不要提高资源的限制值来解决问题。
练习 指定一个源文件,实现copy到目标目录 例如把/tmp/test.txt拷贝到/tmp/test1.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 filename1 = '/tmp/test.txt' filename2 = '/tmp/test1.txt' f = open (filename1, 'w+' ) lines = ['abc' ,'123' ,'magedu' ] f.writelines('\n' .join(lines)) f.seek(0 ) print (f.read())f.close() def copy (src,dest ): with open (src) as f1: with open (dest, 'w' ) as f2: f2.write(f1.read()) copy(filename1, filename2)
有一个文件,对其进行单词统计,不区分大小写,并显示单词重复最多的10个单词 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 简单处理后,大概的得数如下: the, 136 is , 60 a, 54 path, 52 if , 42 and , 39 to, 34 of, 33 on, 32 return , 30 实际上有效的path很多,作为合法的单词path统计应该有100 多个。对单词做进一步处理后,统计如下: path, 137 the, 136 is , 60 a, 59 os, 50 if , 43 and , 40 to, 34 of, 33 on, 33 复制sample.txt文件到指定目录 d = {} with open ('sample' , encoding='utf8' ) as f: for line in f: words = line.split() for word in map (str .lower, words): d[word] = d.get(word,0 ) + 1 print (sorted (d.items(), key=lambda item: item[1 ], reverse=True ))======================================================================================= map ()函数测试a=(1 ,2 ,3 ,4 ,5 ) b=[1 ,2 ,3 ,4 ,5 ] c="zhangkang" la=map (str ,a) lb=map (str ,b) lc=map (str ,c) print (la)print (lb)print (lc)输出: ['1' , '2' , '3' , '4' , '5' ] ['1' , '2' , '3' , '4' , '5' ] ['z' , 'h' , 'a' , 'n' , 'g' , 'k' , 'a' , 'n' , 'g' ] =======================================================================================
这是帮助文档中python的文档,path应该很多
1 2 3 for k in d.keys(): if k.find('path' ) > -1 : print (k)
使用上面的代码,就可以看到path非常多
os.path.exists(path) 可以认为含有2个path。
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 def makekey (s:str ): chars = set (r"""!'"#./\()[],*-""" ) key = s.lower() ret = [] for i,c in enumerate (key): if c in chars: ret.append(' ' ) else : ret.append(c) return '' .join(ret).split() d = {} with open ('sample' ,encoding='utf8' ) as f: for line in f: words = line.split() for wordlist in map (makekey,words): for word in wordlist: d[word] = d.get(word,0 ) + 1 for k,v in sorted (d.items(),key=lambda item: item[1 ],reverse=True ): print (k,v) path, 137 the, 136 is , 60 a, 59 os, 50 if , 43 and , 40 to, 34 of, 33 on, 33
分割key的另一种思路
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 def makekey (s:str ): chars = set (r"""!'"#./\()[],*-""" ) key = s.lower() ret = [] start = 0 length = len (key) for i,c in enumerate (key): if c in chars: if start == i: start += 1 continue ret.append(key[start:i]) start = i + 1 else : if start < len (key): ret.append(key[start:]) return ret print (makekey('os.path.exists(path)' ))print (makekey('os.path.-exists(path)' ))print (makekey('path.os...' ))print (makekey('path' ))print (makekey('path-p' ))print (makekey('***...' ))print (makekey('' ))