Python文件读写操作

  • 原创
  • Madman
  • /
  • 2018-05-31 11:57
  • /
  • 0
  • 419 次阅读

Synopsis: Python中可以使用open()方法来打开一个文件描述符,并指定相应的访问模式,来决定是读取文件还是写入文件。读写完成后,切记要调用close()方法来关闭文件描述符。Python中不仅可以保存数据到磁盘上的文件中,还可以在内存中读写数据,比如StringIO和BytesIO这种file-like object

1. 文件的打开与关闭

现代操作系统不允许应用程序直接操作磁盘,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

Python中的文件读写分为三个步骤:

  • 打开文件描述符r/w/a模式),或者新建文件描述符x模式)
  • 读写数据(read()write()
  • 关闭文件描述符close()

Python内置了open()函数可以打开一个已经存在的文件,或者创建一个新文件。我们只需要传入文件名访问模式,返回一个文件对象:

Help on built-in function open in module io:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
    Open file and return a stream.  Raise IOError upon failure.

如果不指定mode(打开文件的方式),则默认是r,表示只能读取此文件对象中的内容。r模式强调此文件必须已存在,否则会报错FileNotFoundErrorx模式强调此文件必须不能已存在,否则会报错FileExistsError

Mode Character Meaning 说明
r open for reading (default) 只能读取(文件必须已存在),如果文件不存在,则报错FileNotFoundError
w open for writing, truncating the file first 只能写入,如果文件已存在,则先清空原内容再写入(全覆盖);如果文件不存在,则创建新文件并写入
x create a new file and open it for writing 只能创建新文件并写入(不能读取),如果文件已存在,则报错FileExistsError
a open for writing, appending to the end of the file if it exists 只能追加(不能读取),如果文件已存在,则在文件末尾追加内容(一开始文件指针位于文件末尾,就算使用seek(0, 0)定位到文件开头,此时调用write()还是会写入到文件末尾);如果文件不存在,则创建新文件并写入
b binary mode 以二进制格式打开一个文件,比如图片、视频等(此模式下读取或写入文件对象的是bytes
t text mode (default) 以文本格式打开一个文件,比如文本文件(此模式下读取或写入文件对象的是str)。它是默认的,所以r/w/x/a分别相当于rt/wt/xt/at
+ open a disk file for updating (reading and writing) r/w/x/a组合使用,可同时读写文件

常用的组合访问模式:

  • rb: 以二进制格式打开一个文件用于只读(此文件必须已存在),文件指针位于文件的开头
  • wb: 以二进制格式打开一个文件用于写入,如果该文件已存在,则先清空原内容再写入(全覆盖);如果该文件不存在,则创建新文件并写入
  • ab: 以二进制格式打开一个文件用于追加,如果该文件已存在,则在文件末尾追加内容(就算使用seek(0, 0)定位到文件开头,此时调用write()还是会写入到文件末尾);如果该文件不存在,则创建新文件并写入
  • r+: 打开一个文件用于读写(文件必须已存在),如果文件不存在,则报错FileNotFoundError
  • w+: 打开一个文件用于读写,如果文件已存在,则先清空原内容再写入(全覆盖);如果文件不存在,则创建新文件并写入
  • x+创建一个文件用于读写(文件不能已存在),如果文件已存在,则报错FileExistsError
  • a+: 打开一个文件用于读写,如果文件已存在,则在文件末尾追加内容(就算使用seek(0, 0)定位到文件开头,此时调用write()还是会写入到文件末尾);如果文件不存在,则创建新文件并写入
  • rb+/wb+/ab+: 不再复述,也是可同时读写

2. read

2.1 读取文本文件 str

可以指定mode为r,也可以省略。文件名可以是绝对路径或相对路径,相对路径表示当前目录的文件。如果文件不存在,将抛出FileNotFoundError异常:

In [1]: f = open('/tmp/test.txt')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-1-328051eff72d> in <module>()
----> 1 f = open('/tmp/test.txt')

FileNotFoundError: [Errno 2] No such file or directory: '/tmp/test.txt'

如果成功打开文件,可以使用read()方法一次性读取所有内容到内存中,返回str对象。tell()方法返回当前文件指针的位置(从0开始计数),seek(offset, from)重新设置文件指针的位置,其中offset表示偏移量,正数表示往后偏移多少个字节,负数表示往前偏移多少个字节,from表示从哪开始偏移(0:文件开头,1:当前指针位置,2:文件末尾),所以seek(0, 0)表示回到文件开头处,seek(3, 1)表示从当前位置向后偏移3个字节,seek(-3, 2)表示文件末尾倒数第3个字节处

In [2]: f = open('/tmp/test.txt')

In [3]: f.read()
Out[3]: 'aaa\nbbb\nccc\n'

In [4]: f.read()  # 此时文件指针已到文件末尾,所以再次调用read()返回空
Out[4]: ''

In [5]: f.tell()
Out[5]: 12

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

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

In [8]: f.read()
Out[8]: 'aaa\nbbb\nccc\n'

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

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

In [11]: s
Out[11]: 'aaa\nbbb\nccc\n'

In [12]: type(s)  # open()方法默认以t模式即文本模式打开文件对象,读取或写入的数据是str类型(Unicode)
Out[12]: str

In [13]: f.close()

切记: 一旦读写完文件,要记得f.close()关闭文件,因为文件对象会占用操作系统的资源,所以一般操作系统设置可打开的最大文件描述符数量

但是文件读写时可能会产生IO错误,那么后面f.close()可能不会被调用,所以要使用try...finallywith语句:

# 1. 繁琐的try...finally
try:
    f = open('/tmp/test.txt', 'r')
    print(f.read())
finally:
    if f:
        f.close()

# 2. 推荐使用with语句,它会自动帮我们调用f.close()
with open('/tmp/test.txt', 'r') as f:
    print(f.read())

如果文件很大,为防止它撑爆内存,可以给read()方法指定size参数,表示每次最多读取size个字节

read(size=-1, /) method of _io.TextIOWrapper instance
    Read at most n characters from stream.

    Read from underlying buffer until we have n characters or we hit EOF.
    If n is negative or omitted, read until EOF.

或者,使用readline()方法每次读取一行(包含末尾的换行符):

In [1]: f = open('/tmp/test.txt')

In [2]: f.readline()
Out[2]: 'Hello World\n'

In [3]: f.readline()
Out[3]: 'I love Python\n'

In [4]: f.readline()
Out[4]: 'My Name is wangy\n'

In [5]: f.readline()
Out[5]: ''   # 已经没有内容了

In [6]: f.close()

readlines()方法一次读取所有内容并按行返回list对象:

In [1]: f = open('/tmp/test.txt', 'r')

In [2]: f.readlines()
Out[2]: ['Hello World\n', 'I love Python\n', 'My Name is wangy\n']

In [3]: f.close()

In [4]: f2 = open('/tmp/test.txt', 'r')  # 重新打开一个文件对象

In [5]: for line in f2.readlines():
   ...:     print(line.strip())  # 去除末尾的换行符

Hello World
I love Python
My Name is wangy

In [6]: f2.close()

总结:如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便

2.2 读取二进制文件 bytes

要读取像图片、程序、视频等二进制文件,需要使用rb模式打开文件,返回的是bytes

In [1]: f = open('/tmp/favicon.ico', 'rb')

In [2]: f.read()
Out[2]: b'\x00\x00\x01\x00\x01\x00 ...'

In [3]: f.close()

2.3 指定字符编码

open()方法如果不指定encoding,即使用当前系统中的默认编码,比如CentOS中是UTF-8,而Win10中是cp936深入研究字符编码,告别Python乱码

# 1. CentOS-7.3中不能读取GBK编码的内容
In [1]: f1 = open('/tmp/gbk.txt', 'r')

In [2]: f1
Out[2]: <_io.TextIOWrapper name='/tmp/gbk.txt' mode='r' encoding='UTF-8'>

In [3]: f1.read()
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-3-5c2a67765a29> in <module>()
----> 1 f1.read()

/usr/local/python-3.6/lib/python3.6/codecs.py in decode(self, input, final)
    319         # decode input (taking the buffer into account)
    320         data = self.buffer + input
--> 321         (result, consumed) = self._buffer_decode(data, self.errors, final)
    322         # keep undecoded input until the next call
    323         self.buffer = data[consumed:]

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte

In [4]: f1.close()

# 2. Win10可以正确读取GBK编码的内容
c:\Flask>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> f1 = open('gbk.txt', 'r')
>>> f1
<_io.TextIOWrapper name='gbk.txt' mode='r' encoding='cp936'>
>>> f1.read()
'中国'
>>> f1.close()
>>> 

此时,CentOS中可以在open()方法中指定encoding='gbk'

In [1]: f = open('/tmp/gbk.txt', 'r', encoding='gbk')

In [2]: f
Out[2]: <_io.TextIOWrapper name='/tmp/gbk.txt' mode='r' encoding='gbk'>

In [3]: f.read()
Out[3]: '中国'

In [4]: f.close()

3. write

w模式打开文件对象,表示写入文本文件,以wb模式打开文件对象,表示写入二进制文件。然后调用write()方法写入即可,需要注意的是,此时操作系统一般不会立即将数据写入磁盘,而是放在内存中缓存起来,只有调用f.close()方法时才会写入磁盘,所以最保险的方式是使用with语句:

with open('/tmp/test.txt', 'w') as f:
    f.write('Hello, \n')
    f.write('world\n')  # 可以重复调用write()

4. append

只指定w时,如果文件已存在,会直接覆盖原内容,如果要追加内容到文件的末尾,可以使用a模式。如果文件已存在,则在文件末尾追加内容(一开始文件指针位于文件末尾,就算使用seek(0, 0)定位到文件开头,此时调用write()还是会写入到文件末尾);如果文件不存在,则创建新文件并写入

5. create

x模式可以创建一个新的文件对象,如果文件已存在,则报错FileExistsError。然后用write()方法写入数据,调用f.close()后就可以创建一个新的文件了

6. file-like object

open()函数返回的这种有个read()方法的对象,在Python中统称为file-like object。除了文件对象外,还可以是内存的字节流,网络流,自定义流等等。file-like object不要求从特定类继承,只要写个read()方法就行。

6.1 StringIO

数据读写除了可以使用文件对象以外,还可以在内存中读写。StringIO顾名思义就是在内存中读写str对象(Unicode)

# 1. 创建StringIO对象,调用write()像写文件一样写入,用getvalue()获得写入后的str
In [1]: from io import StringIO

In [2]: f = StringIO()

In [3]: f.write('hello, ')
Out[3]: 7

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

In [5]: f.getvalue()
Out[5]: 'hello, world'

In [6]: f.read()
Out[6]: ''

In [7]: f.tell()
Out[7]: 7

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

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

In [10]: f.read()
Out[10]: 'hello, world'

# 2. 使用str来初始化StringIO对象,此时还可以调用read()、readlines()等像读文件一样读取
In [1]: from io import StringIO

In [2]: f = StringIO('Hello!\nHi!\nGoodbye!')

In [3]: while True:
   ...:     s = f.readline()
   ...:     if s == '':
   ...:         break
   ...:     print(s.strip())
   ...:     
Hello!
Hi!
Goodbye!

6.2 BytesIO

在内存中读写bytes对象(字节流)

In [1]: from io import BytesIO

In [2]: f = BytesIO()

In [3]: b = '中国'.encode('utf-8')

In [4]: b
Out[4]: b'\xe4\xb8\xad\xe5\x9b\xbd'

In [5]: type(b)
Out[5]: bytes

In [6]: f.write(b)
Out[6]: 6

In [7]: f.getvalue()
Out[7]: b'\xe4\xb8\xad\xe5\x9b\xbd'

StringIO类似,可以用一个bytes初始化BytesIO

In [1]: from io import BytesIO

In [2]: f = BytesIO(b'\xe4\xb8\xad\xe5\x9b\xbd')

In [3]: f.read()
Out[3]: b'\xe4\xb8\xad\xe5\x9b\xbd'
未经允许不得转载: LIFE & SHARE - 王颜公子 » Python文件读写操作

分享

作者

作者头像

Madman

如果博文内容有误或其它任何问题,欢迎留言评论,我会尽快回复; 或者通过QQ、微信等联系我

0 条评论

暂时还没有评论.

发表评论前请先登录