Python的IO编程
操作IO的能力都是有操作系统决定的,因此,使用高级语言来操作都是调用的系统所提供的低级C接口。
本文探讨的IO都是同步的
非二进制文件读写
向其他脚本语言相似,Python提供了以下非二进制文件IO模式:
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
w | 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
w+ | 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
总结一下就是:
凡是有
+
的都可以读写,只有r
模式不会创建新文件(抛出IOError的异常),w
和a
都会在没有找到文件时创建,只有a
的初始指针在文件尾。
基本API:
使用open()
来打开一个文件,从
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
可看出,默认的模式为 r ,必要参数只有一个,那就是file
,这里,file
是一个 path-like-object
,除了我们认为的文件,还能打开内存的字节流,网络流,自定义流等等。
这样我们便得到了一个file
对象,有用的属性有:
属性 | 描述 |
---|---|
file.closed | 返回文件是否已关闭 |
file.name | 返回文件的名字 |
file.mode | 返回文件的打开模式 |
使用read()
来读取一个文件的全部内容,Python会把内容全部塞到内存中,用一个str
,来表示。
使用write()
来向一个文件写入内容(注意:操作系统往往不会在我们调用write()时就立即写入数据,而是先暂存到内存中,空闲时在慢慢写入(这个可以使用交互式的Shell来试一下:执行写入操作后先不要关闭句柄,到资源管理器中看一下,会发现文件大小不变)),而当我们调用close()
方法时,OS才会去确保把数据全部写入。
因此,必须在文件使用完毕后调用close()
方法(否则可能会有数据滞留内存中)。
如果想要立即写入数据,可以调用flush()
方法来刷新文件内部缓冲,相当于是主动写入。
如果想要知道当前文件指针的位置,则可以直接调用tell()
方法,返回文件指针的位置(字节数)
使用seek(offset[,from])
方法来调整指针位置,参数是支持负数的(也就是向前偏移)
在做IO时一般都要考虑IO异常的产生,因此要进行异常处理:
1 | try: |
这样写比较繁琐,所以Python提供了with
语法糖:
1 | with open("sample", "r") as f: |
这样就使得代码变得更加简洁,而且不需要再调用close
方法。
回到前面说的,read
方法会把文件全部读取到内存中,这对于小文件是可行的,如果文件大小超过了8G,内存可能就爆了。
所以,最好使用反复调用read(size)
这样来读取,或是使用readlines()
来读取,这种读取方法非常适合读取配置文件或单行文本。
1 | for line in f.readlines(): |
二进制IO和FS操作
对于二进制的文件操作基本与之前的文件读写操作类似,不同的是访问模式:(也就是在之前的访问模式后加b = _binary_)
模式 | 描述 |
---|---|
rb | 以只读方式打开一个二进制文件。文件的指针将会放在文件的开头。 |
rb+ | 打开一个二进制文件用于读写。文件指针将会放在文件的开头。 |
wb | 打开一个二进制文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
wb+ | 打开一个二进制文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
ab | 打开一个二进制文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab+ | 打开一个二进制文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
对于文件系统的操作,主要使用一个在Python中非常重要的模块 – os
.
先来看一下os
的一些基本属性吧。
os中有很多API在Windows下是不可用的,如果想让os发挥最大作用,也许你需要一台高贵的Mac或者一只免费的penguin
使用os.name
,获取系统类型 : 如windows
为nt
,Linux/Unix/Mac OS X
为posix
使用os.uname()
,获取详细的系统信息, 不支持Windows
os还提供了一系列的方法来进行文件系统的操作,就好像在跑命令行一样。
来看一个综合实例:
1 | #! /usr/bin/env python3 |
也许你会奇怪,为什么os
模块中并没有封装剪切、复制这样的常用方法。(虽然我们已经可以使用上面的IO解决)
铛铛铛! Python为我们封装了shutil
模块。shutil
提供了各种剪切复制方法(文件,对象,信息,模式…都可以)
事实上他的复制也就是我们所想的那样:
删除了部分注释
特意又去看了2.7的源码…基本没有改动(3.6增加了对文件链接的处理、以及3.3/3.4对异常的更改)
1 | def copyfile(src, dst, *, follow_symlinks=True): |
序列化
Python支持bytes序列化和流行的JSON序列化。
本文仅谈一下JSON的部分。
JSON中允许的数据类型有:
- {}
- []
- “string”
- 1234.56
- true/false
- null
与之对应的Python数据类型为:
- dict
- list
- str
- int/float
- True/False
- None
使用起来也很容易,使用dumps()
来进行序列化,使用loads()
来反序列化
1 | import json |
既然使用起来这么方便,那么我们可否直接序列化一个类的实例呢?
答案是:可以
比方说我们定义一个Student类,有name,age,score参数,我们便可以定义这样的方法:
1 | def student2dict(std): |
接着,在序列化该实例时,调用:
1 | s = Student("Justin", 19, 100) |
就可以按照我们的想法来序列化了。