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) |
就可以按照我们的想法来序列化了。