Python3 IO编程(v3.7)

[TOC]

文件读写

Python内置读写文件函数,用法与C语言兼容。在磁盘上读写文件的功能都是由操作系统提供的,现代系统不允许普通程序直接操作磁盘。

读文件

Python内置open()函数读取文件对象,如果文件不存在函数会抛出IOError错误。

1
2
f = open('/Users/Windus/person.py', 'r')
f.read() #读取全部内容

Python把内容读取到内存中,用str对象表示,read()会一次性读取所有内容,如果文件过大内存就会溢出,所以也可以调用read(size)函数限制每次最多读取多少字节(size)的内容;readline()每次读取一行;readlines一次读取所有内容并返回list。

打开一个文件对象后,必须调用close()方法关闭,因为文件对象会占用操作系统资源,并且操作系统同一时间能打开的文件数量也是有限的。

1
f.close() #关闭文件对象

因为文件读写时有可能产生IOError,一旦出错后面close()将不会执行,为了保证无论错误与否都能关闭文件对象,需要使用try...finally

1
2
3
4
5
6
try :
f = open('/Users/Windus/person.py', 'r')
print(f.read()) #读取全部内容
finally :
if f:
f.close()

上面写法太过麻烦,Python中引入with语句来简化此写法,下面写法等同于try…finally写法,但是更加简洁且可省略调用close()方法:

1
2
with open('/Users/Windus/person.py', 'r') as f:
print(f.read())

open()函数还可以传入encoding参数设置文件编码,errors参数设置遇到错误如何处理,如设置ignore忽略错误。

1
>>> f = open('/Users/Windus/person_gbk.py', 'r', encoding='gbk', errors='ignore')

写文件

写文件与读取唯一区别就是open()标识符参数设置为wwb表示写入文本文件或二进制文件。然后反复调用write()方法把字符写入文件。

1
2
3
f = open('/Users/Windus/person.py', 'w')
f.write('Hello IO')
f.close()

写入文件时,操作系统不一定会立刻把数据写入磁盘,而是放入内存中缓存起来,空闲时再写入。只有调用close()方法时,才能保证把没有写入磁盘的数据全部写入,所以写入文件后一定要调用close()方法,或者直接使用with语句。

1
2
with f = open('/Users/Windus/person.py', 'w') as f:
f.write('Hello IO')

open()标识符w或wb时,如果文件已存在会被覆盖,如果想要追加信息需要使用a标识符,更多方法参考官方文档

file-like Object

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

内存IO

有时候数据读写不一定是文件,也可以在内存中读写。此时可以使用StringIO和BytesIO。

StringIO

StringIO需要引入io模块中的StringIO类,用于读写内存中的字符数据。

1
2
3
4
5
from io import StringIO

f = StringIO()
f.write('Hello StringIO')
print(f.getvalue()) --> Hello StringIO

BytesIO

BytesIO需要引入io模块中的BytesIO类,用于读写内存中的二进制数据。

1
2
3
4
5
from io import BytesIO

f = BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())

StringIO和BytesIO都可以通过初始化赋值,然后通过read()等方法读取

1
2
3
4
5
6
7
8
from io import StringIO

f = StringIO('abc\ndef')
while True :
s = f.readline()
if s == '':
break
print(s.strip())

文件与目录操作

Python内置os模块提供了相关操作函数。os中的某些函数是与操作系统相关的,如:os.uname()在Windows系统中不提供此函数。

  • os模块常用的属性和函数

    1
    2
    3
    os.name #操作系统类型posix表示系统是Linux、Unix或Mac OS X,如果是nt,表示Windows系统
    os.uname() #系统详细信息(Windows系统不提供此函数)
    os.environ #获取系统环境变量,可以通过get函数获取具体变量值os.environ.get('PATH')
  • 文件与目录操作

操作文件与目录的函数一部分放在os模块中,一部分放在os.path模块中,常用函数如下:

1
2
3
4
5
6
os.path.abspath('.') #查看当前目录的绝对路径
os.path.join('/Users/Windus','testdir') #把两个路径合并为一个路径
os.mkdir('/Users/Windus/testdir') #创建目录
os.rmdir('/Users/Windus/testdir') #删除目录
os.rename('test.txt','test.py') #文件重命名
os.remove('test.py') #删除文件

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分。

os模块中不存在复制文件函数,原因在于复制文件并非由操作系统提供的系统调用,可以通过文件读写完成复制,也可以通过shutil模块提供的copyfile()函数完成复制,此模块中还包含很多有用的方法,可以看作是os模块的补充模块。

序列化

把变量从内存中变成可存储或传输的过程称之为序列化,Python中序列化叫作pickling,反序列化称为unpickling。Python提供了pickle模块实现序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pickle

d = dict(name='Bob', age=20, score=88)
pickle.dumps(d) #序列化一个字典对象为file-like Object

|
|
V

f = open('dump.txt', 'wb')
pickle.dump(d, f) #序列化一个字典对象并写入文件
f.close()

|
|
V

f = open('dump.txt', 'rb')
d = pickle.load(f) #反序列化文件内容到一个对象
f.close()

pickle和所有编程语言序列化问题都一样,就是它只能作用于自己的语言(Python),并且不同版本Python可能都不兼容。因此,只能用pickle保存不能成功反序列化也没有关系的不重要数据。

JSON

字典的序列化操作

在不同语言间传递对象,须要使用标准对象格式,如:XML。更好的方法是序列为JSON。Python内置json模块提供JSON处理。

1
2
3
4
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d) #把Python对象转换为JSON对象
'{"age": 20, "score": 88, "name": "Bob"}'

JSON序列化通过dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。
把JSON反序列化为Python对象通过load()loads()方法,前者可以把JSON字符串反序列化,后者可以从file-like Object中读取字符串并反序列化。

class的序列化操作

像上述方法通过json.dumps()序列化一个类会报TypeError,想要把一个类序列化需要使用dumps()方法的另一个可选参数default,只需要为一个类写一个转换函数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(object):
def __init__(self,name,age):
self.name= name
self.age = age

def person2dict(p):
return {
'name': p.name,
'age': p.age
}

>>> import json
>>> p = Person('张三',30)
>>> json.dumps(p,default=person2dict)
{'name': '张三','age': 30}

通过转换函数虽然实现了类的序列化,但是针对不同的类,需要写相应的转换类,可以通过__dict__来简化此问题。通常class都有一个__dict__属性,它就是一个dict用来存储实例变量,也有少数例外,比如定义了slots的class。

1
json.dumps(p,default=lambda obj: obj.__dict__)

把JSON反序列化为一个Person对象实例,loads()方法首先转换出一个dict对象,然后,我们传入的object_hook函数负责把dict转换为Student实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def dict2person(d):
return Student(d['name'], d['age'], d['score'])

d = {'name': '张三','age': 30}
json.loads(json_str, object_hook=dict2person)

| 优 |
| 化 |
| 代 |
V 码 V

class JSONObject(object):
def __init__(self,d):
self.__dict__ = d #直接把json对象转换为dict

>>> data = json.loads(json_str, object_hook=JSONObject)
>>> data.name
'张三'

通过json.dumps()序列化后中文默认使用ascii编码,如果想要显示真正的中文需要加上可选参数ensure_ascii=False