Python 文件操作

文件IO常见操作,open()函数,open函数各个参数(mode、buffering、encoding、errors、newline、closefd)、文件指针、缓冲区、上下文管理一一介绍并举例。

 

 

文件IO常见操作:

 

Open()函数:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
打开一个文件,返回一个文件对象(流对象)和文件描述符。打开文件失败,则返回异常

基本使用:

创建一个文件test,然后打开它,用完关闭

 

参数:file

open 的file 参数:

打开或者要创建的文件名。如果不指定路径,默认是当前路径

In [1]: f = open('c:\\test.txt') #file对象,文件不存在返回异常 FileNotFoundError: [Errno 2] No such file or directory: 'c:\\test.txt'

In [2]: f = open('c:\\test') #打开文件

In [3]: f

Out[3]: <_io.TextIOWrapper name='test6' mode='w' encoding='cp936'> #windows默认cp936(code page),也就是GBK编码

Out[112]: <_io.TextIOWrapper name='test6' mode='w' encoding='UTF-8'> #linux默认是UTF-8编码

In [4]: f.read() #读取文件

In [5]: f.close() #关闭文件

 

参数:mode 模式

文件操作中,最常见的操作就是读和写。
文件访问的模式有两种:文本模式二进制模式。不同模式下,操作函数不尽相同,表现的结果也不一样。

 

 

 

下面依次举例介绍每个描述字符意义:

 

open 默认是只读模式r打开已经存在的文件

在上面的例子中,可以看到默认是文本打开模式,且是只读的。

r模式 (默认只读)

In [12]: f = open('c:\test') #默认只读打开

In [13]: f.read() #空文件
Out[13]: ''

In [14]: f.write('mag') #只读模式尝试写入内容返回异常 UnsupportedOperation: not writable

In [15]: f.close() #关闭文件

 
In [16]: f = open('c:\test','r') # 'r' 模式可写可不写,默认就是只读模式

In [17]: f.write('mag') #同样返回异常UnsupportedOperation: not writable

In [18]: f.close() #关闭文件

 

w模式(只写)

In [19]: f = open('c:\test','w') #只写方式打开

In [20]: f.read() #不支持读取,异常:UnsupportedOperation: not readable

In [21]: f.write('mag')
Out[21]: 3

In [22]: f.close() #关闭文件

In [13]: !TYPE test #看看内容,ipython下可以使用!TYPE或者!cat 分别调用windows和linux下查看文件内容命令

mag

In [35]: f.closed #closed属性可以判断文件是否已经关闭,True表示已关闭,Flase未关闭
Out[35]: True

 

In [43]: f = open('test1','w') #如果文件不存在,会创建并打开

In [45]: f.write('magedu')
Out[45]: 6

In [46]: f.close() #关闭文件

In [47]: !TYPE test1

magedu

  

r
只读打开文件,如果使用write方法,会抛异常 UnsupportedOperation: not writable
如果文件不存在,抛出 FileNotFoundError 异常

w
表示只写方式打开,如果读取则抛异常 UnsupportedOperation: not readable
如果文件不存在,则直接创建文件
如果文件存在,则清空文件内容

In [48]: f = open('test2','x') #不存在则创建

In [49]: f.read() #'x' 模式可写,不可读 异常:UnsupportedOperation: not readable

In [50]: f.write('python')
Out[50]: 6

In [51]: f.close() #关闭文件

 

In [52]: f = open('test2','x') #文件已存在抛异常:FileExistsError: [Errno 17] File exists: 'test2'

  

x
文件不存在,创建文件,并只读方式打开
文件存在,抛出异常 FileExistsError

In [59]: !TYPE test2

python

In [60]: f = open('test2','a') #已存在文件

In [61]: f.read() #异常 UnsupportedOperation: not readable

In [62]: f.write('\n hello')
Out[62]: 5

In [63]: f.close() #关闭文件

In [64]: !TYPE test2
python
hello

 

In [65]: f = open('test3','a') #文件不存在则创建,不可读,可写

In [66]: f.read() #异常UnsupportedOperation: not readable

In [67]: f.write('world')
Out[67]: 5

In [68]: f.close() #关闭文件

In [69]: !TYPE test3
world

  

a
文件存在,只写打开,追加内容
文件不存在,则创建后,只写打开,追加内容

r是只读,wxa都是只写。
wxa都可以产生新文件,w不管文件存在与否,都会生成全新内容的文件;a不管文件是否存在,都能在打开的文件尾部追加;x必须要求文件事先不存在,自己造一个新文件

 

文本模式t
字符流,将文件的字节按照某种字符编码理解,按照字符操作。open的默认mode就是rt。

 

二进制模式b
字节流,将文件就按照字节理解,与字符编码无关。二进制模式操作时,字节操作使用bytes类型

In [70]: f = open('test3','rb') #二进制只读

In [71]: s = f.read()

In [72]: print(type(s))
<class 'bytes'>

In [73]: print(s)
b'world'

In [74]: f.close() #关闭文件

 

In [75]: f = open('test3','wb') #IO对象

In [76]: s = f.write('马哥教育'.encode()) #'b' 二进制模式必须使用b'马哥教育'或者encode()转成bytes类型

In [77]: print(type(s)) #'int'类型
<class 'int'>

In [78]: print(s) #返回是什么
12

In [79]: f.close() #关闭文件

In [80]: !TYPE test3
椹摜鏁欒偛 #乱码原因后面介绍

 

In [87]: f = open('test3','x+') #'x' 只能打开不存在文件,否则返回异常 FileExistsError

In [88]: f = open('test4','x+')

In [89]: f.write('devops')
Out[89]: 6

In [90]: f.read() #思考:上一步已经写入,为什么读取不到?(缓冲区,需要flush)
Out[90]: ''

In [91]: f.close()

In [92]: !type test4
devops

  

+
为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

 

 

 

# 文本模式

In [130]: f = open('test5','r+')

In [131]: f.tell() # 起始
Out[131]: 0

In [132]: f.read()
Out[132]: 'secopsmetasploit'

In [133]: f.tell() # EOF
Out[133]: 16

In [134]: f.seek(0) #指针移到起始
Out[134]: 0

In [135]: f.read()
Out[135]: 'secopsmetasploit'

In [136]: f.seek(2,0)
Out[136]: 2

In [137]: f.tell()
Out[137]: 2

In [138]: f.read()
Out[138]: 'copsmetasploit'

In [139]: f.seek(2,1) #offset只能为0
UnsupportedOperation: can't do nonzero cur-relative seeks

In [140]: f.seek(2,2) #offset只能为0
UnsupportedOperation: can't do nonzero end-relative seeks

In [141]: f.close()

  

二进制模式下
whence 0 缺省值,表示从头开始,offset只能正整数
whence 1 表示从当前位置,offset可正可负
whence 2 表示从EOF开始,offset可正可负
EOF(end of file,即文件结尾,终点)

 

 

 

 

#二进制模式

In [111]: f = open('test5','r+b')

In [112]: f.tell() #起始
Out[112]: 0

In [113]: f.read() #读取完指针移到EOF
Out[113]: b'secops'

In [114]: f.tell() #EOF
Out[114]: 6

In [115]: f.write(b'metasploit')
Out[115]: 10

In [116]: f.tell()
Out[116]: 16

In [117]: f.seek(0) #移到起始
Out[117]: 0

In [118]: f.seek(2,1) #当前位置向后偏移2位
Out[118]: 2

In [119]: f.read()
Out[119]: b'copsmetasploit'

In [120]: f.seek(-2,1) #当前位置向前偏移2位
Out[120]: 14

In [121]: f.seek(-3,1)
Out[121]: 11

In [122]: f.seek(2,2) #从EOF开始
Out[122]: 18

In [123]: f.seek(0)
Out[123]: 0

In [125]: f.seek(-2,1) #异常,向前偏移不可越界
OSError: [Errno 22] Invalid argument

In [126]: f.tell()
Out[126]: 0

In [127]: f.read()
Out[127]: b'secopsmetasploit'

In [128]: f.seek(-20,2) #异常,向前偏移不可越界
OSError: [Errno 22] Invalid argument

In [129]: f.close()

  

二进制模式支持任意起点的偏移,从头、从尾、从中间位置开始。
向后seek可以超界,但是向前seek的时候,不能超界,否则抛异常。

 

 

参数:buffering 缓冲区

 

-1 表示使用缺省大小的buffer。
如果是二进制模式,使用 io.DEFAULT_BUFFER_SIZE 值(缺省缓冲区大小,字节),默认是4096或者8192 字节。
如果是文本模式,或者终端设备,是行缓存模式,如果不是,则使用二进制模式的策略。
0 只在二进制模式使用,表示关buffer
1 只在文本模式使用,表示使用行缓冲。意思就是见到换行符就flush
大于1 用与指定buffer的大小,如果不指定或者负数,表示使用缺省大小

 

 

 

 

 


flush() 将缓冲区数据写入磁盘
close() 关闭前会调用flush()

buffer 缓冲区
缓冲区一个内存空间,一般来说是一个FIFO队列(file input file output),到缓冲区满了或者达到阈值,数据才会flush到磁盘

In [142]: import io

In [143]: io.DEFAULT_BUFFER_SIZE   #系统默认buffer大小
Out[143]: 8192

  

 

buffering 说明
buffering=-1 t和b,都是 io.DEFAULT_BUFFER_SIZE 大小
buffering=0 b关闭缓冲区
t不支持  (文件模式简写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 表示使用缺省编码,依赖操作系统。win、linux下测试如下代码

In [148]: f = open('test6','w')

In [149]: f
Out[149]: <_io.TextIOWrapper name='test6' mode='w' encoding='cp936'> #linux下是UTF-8

In [150]: f.write('啊')
Out[150]: 1

In [151]: f.close()

  

其它参数:


errors:什么样的编码错误将被捕获
None和strict表示有编码错误将抛出ValueError异常;ignore表示忽略

newline:文本模式中,换行的转换。可以为None、''、'\r'、'\n'、'\r\n'
读时,None表示 '\r'、'\n'、'\r\n' 都将转换为'\n';'' 表示不会不会自动转换通用换行符;其它合法字符表示换行符就是指定字符,就会按照指定字符分行
写时,None表示'\n'(win)或'\r\n'(linux)都会被替换为系统缺省行分割符os.linesep;'\n'或''表示'\n'不转换;其它合法字符表示'\n'(win)或'\r\n'(linux)会被替换为指定的字符

In [155]: import os

In [156]: os.linesep
Out[156]: ' '


closefd:关闭文件描述符,True表示关闭它。False会在文件关闭后保持这个描述符。
fileobj.fineno()查看

  

read方法

read(size=-1)
size 表示读取的多少个字符或字节;负数或者None表示读到EOF

 

readline方法 行读取

readline(size=-1)
一行行读取文件内容。size设置依次能够读取行内几个字符或者字节。
readlines(hin=-1)
读取所有行的列表。指定hint则返回指定的行数。

 

close方法

flush并关闭文件对象。
文件已经关闭,再次关闭没有任何效果。


其他方法
seekable() 是否可seek
readable() 是否可读
write() 是否可写
closed 是否已经关闭

 

 

上下文管理

问题的引出
在linux中,执行

(3.5.3/envs/magedu353) [python@ihoneysec cmdb]$ touch test
(3.5.3/envs/magedu353) [python@ihoneysec cmdb]$ vim context.py #写入以下代码
lst = []
for _ in range(2000):
lst.append(open('test'))
print(len(lst))
  

(3.5.3/envs/magedu353) [python@ihoneysec cmdb]$ python context.py
Traceback (most recent call last):
File "context.py", line 3, in <module>
OSError: [Errno 24] Too many open files: 'test'
(3.5.3/envs/magedu353) [python@ihoneysec cmdb]$ lsof|wc -l 1068

  


lsof 列出打开的文件(list open files)
$ lsof |grep test|wc -l
ulimit -a查看所有限制。其中open files就是打开文件数的限制,默认1024
$ ulimit -n 65535 #可以修改为65535,但要根据硬件资源调整为合适的大小

 

for x in lst:
    zx.close()

  

将文件依次关闭,然后就可以继续打开了。再看一次lsof。

 

如何解决?
1、异常处理
当出现异常的时候,拦截异常。但是,因为很多代码都可能出现OSError异常;还不好判断异常就时因为资源限制产生的

f = open('test')
try:
    f.write('abc') #文件只读,写入失败
finally:
    f.close() #finally 可以保证打开的文件可以被关闭。

  


2、上下文管理
一种特殊的语法,交给解释器取释放文件对象

with open('test6') as f:
    f.write('abc')
    f.closed #测试f是否关闭

  

上下文管理
使用with..as 关键字
上下文管理的语句块并不会开启新的作用域
with语句块执行完的时候,会自动关闭文件对象

 

 

对于类似于文件对象的IO对象,一般来说都需要在不使用的时候关闭、注销,以释放资源。
IO被打开的时候,会获得一个文件描述符。计算机资源是有限的,所有操作系统都会做限制。
就是为了保护计算机的资源不要被完全耗尽,计算机资源是共享的,不是独占的。
一般情况下,除非特别明确的知道资源情况,否则不要提高资源的限制值来解决问题。

 

posted @ 2017-10-26 12:46  ihoneysec  阅读(729)  评论(0编辑  收藏  举报