0%

Python - Theme 8 IO programming

0. 知识背景

IO- Input/Output: 通常涉及数据交换的地方都需要IO接口,例如: 磁盘、网络等
基本概念:input, output, stream
存在问题:输入和输出速度不匹配
解决方法:同步、异步(回调: 好了叫我, 异步: 好了没…好了没)

1. 文件读写

在磁盘上读写文件的功能都是由操作系统提供的,不过不允许程序直接操作磁盘。因此,读写文件其实是程序请求操作系统打开一个文件对象(文件描述符)来实现读写。

1.1. 读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#示例文件内容(当前路径下)
# This is for read and write test
This is 1st line
This is 2nd line
This is 3rd line

#一次简单的读文件过程
>>> f = open('test', 'r') #打开当前目录下名为test的文件
Traceback (most recent call last):
File "<input>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'test' #如果文件不存在,就会抛出错误
>>> f = open('rwtest', 'r') #成功打开了一个文件,'r':读
>>> f.read() #一次性把文件内容全部读入内存,然后打印出来
'# This is for read and write test\nThis is 1st line\nThis is 2nd line\nThis is 3rd line'
>>> f.close() #关闭文件,避免资源浪费


#由于在读写文件的时候无论如何都要close(),python提供了一种简洁通用的写法
(1). read() -小文件一次性读取
>>> with open('rwtest', 'r') as f:
... content = f.read()
... print(type(content))
... print(content)
...
<class 'str'>
# This is for read and write test
This is 1st line
This is 2nd line
This is 3rd line

(2). read(size) -因内存考虑,可以反复的按照bytes读取
>>> with open('rwtest', 'r') as f:
... content = f.read(8) #size: bytes
... print(type(content))
... print(content)
...
<class 'str'>
# This i

(2). readline() - 文件逐行读入,并将'/n'也读入了,因此最好splitlines()掉换行符
>>> with open('rwtest', 'r') as f:
... line = f.readline()
... print(type(line))
... print(line)
... while line:
... print(line)
... line = f.readline()
...
<class 'str'>
# This is for read and write test
# This is for read and write test

This is 1st line

This is 2nd line

This is 3rd line

(3). readlines() - 读取整个文件所有行,存入list,每行为一个元素
>>> with open('rwtest', 'r') as f:
... lines = f.readlines()
... print(type(lines))
... print(lines)
...
<class 'list'>
['# This is for read and write test\n', 'This is 1st line\n', 'This is 2nd line\n', 'This is 3rd line']

1.2. 写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> with open('test','w') as f: #如果文件不存在就直接创建并写入,如果存在就覆盖
... f.write('Hello')
...
>>> with open('test','r') as f:
... print(f.read())
...
Hello

>>> with open('rwtest','a') as f: #'a':在文件后追加
... f.write('Hello')
...
>>> with open('rwtest','r') as f:
... print(f.read())
...
# This is for read and write test
This is 1st line
This is 2nd line
This is 3rd lineHello

1.3. 二进制文件

图片、视频等都为二进制文件

1
2
3
4
>>> with open('WechatIMG191.png', 'rb') as f:
... print(f.read())
...
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01,......'

1.4. 字符编码

读取非UTF-8的文本文件,需要给open()函数传入’encoding=’ 参数

2. StringIO 和 BytesIO - python特性: 类文件对象(file-like object)

如果短时间的重复利用,也不希望持久化且对速度的要求较高,则可以使用内存级别的IO。

2.1. StringIO

在内存中有一个标志位的概念,向里面写入,标志位会后移到下一个空白处;然而读取数据的时候也是从标志位开始读的,因此需要手动移动标志位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> from io import StringIO
s = StringIO()
>>> type(s)
<class '_io.StringIO'>
>>> s.write('Hello\nWorld\n!')
13
>>> s.seek(0) #操作标志位移动
0
>>> s.read()
'Hello\nWorld\n!'
>>> s.getvalue() #直接获取全部值
'Hello\nWorld\n!'

>>> f = StringIO('Hello\nWorld\n!')
>>> while True:
... s = f.readline() #这里和读写文件的方法一致
... if s == "":
... break
... print(s.strip())
...
Hello
World
!

2.2. BytesIO

StringIO用于字符串的存储,对于图像、视频等Bytes类型的内容就需要用到BytesIO对象。

1
2
3
4
5
6
7
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write("中国".encode('utf-8'))
6
>>> f.getvalue()
b'\xe4\xb8\xad\xe5\x9b\xbd'
>>> f.close()

3. 操作文件和目录

python的os模块可以直接调用操作系统提供的接口函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import os
>>> os.name #posix指Unix系列
'posix'
>>> os.uname() #显示了更详细的信息
posix.uname_result(sysname='Darwin', nodename='AarondeMacBookPro.local',....太多了省略)
>>> os.environ #显示系统环境信息
environ({'PATH': '/Users/...', ....,'HOME': '...'})
>>> os.environ.get('PATH') #可以只获取某个信息
'/Users/...'
>>> os.path.abspath('.') #显示当前绝对路径
'/Users/...'
>>> os.path.join('/user/user1','testdir') #拼凑路径字符串
'/user/user1/testdir'
>>> os.path.split('/user/user1/testdir') #拆分路径字符串
('/user/user1', 'testdir')
>>> os.path.splitext('/user/user1/test.txt') #文件后缀
('/user/user1/test', '.txt')
>>> os.listdir() #当前路径下文件
['rwtest', 'WechatIMG191.png', '.DS_Store', 'test', 'test4.py', 'Bitcoin.py', 'test1.py', 'testpackage', 'test2.py', 'test3.py', '.idea']
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] #查找当前目录下的.py文件
['test4.py', 'Bitcoin.py', 'test1.py', 'test2.py', 'test3.py']

4. 序列化

序列化(pickling) : 把变量从内存中变成可存储或传输的过程
反序列化(unpickling) : 把变量从序列化对象重新读到内存中

4.1. pickle - 只能用于python

1
2
3
4
5
6
7
8
9
10
11
12
>>> import pickle
>>> d = dict(name = 'Tom', age = 18)
>>> pickle.dumps(d) #将一个字典序列化
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x03\x00\x00\x00Tomq\x02X\x03\x00\x00\x00ageq\x03K\x12u.'
>>> f = open('dump.txt', 'wb') #以写入bytes的方式打开文件dump.txt
>>> pickle.dump(d, f) #将字典以序列化的方式写入文件
>>> f.close()
>>> f = open('dump.txt', 'rb') #以读bytes的方式打开dump.txt
>>> d = pickle.load(f) #反序列化
>>> f.close()
>>> d #得到了内容相同的d变量
{'name': 'Tom', 'age': 18}

4.2. JSON - 一种通用型序列化标准格式

1
2
3
4
5
6
7
>>> import json
>>> d = dict(name = 'Tom', age = 18)
>>> json.dumps(d) #返回一个标准json格式的str
'{"name": "Tom", "age": 18}'
>>> json_str = json.dumps(d)
>>> json.loads(json_str) #反序列化
{'name': 'Tom', 'age': 18}

一般我们习惯用类(class)来定义对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import json

class People(object):
def __init__(self, name, age):
self.name = name
self.age = age


def peopel2dict(people): #将实例转化为dict
return {
'name': people.name,
'age': people.age
}

def dict2people(p):
return People(p['name'], p['age'])

p = People('Tom', 18)
print(p)

#序列化
print(json.dumps(p, default=peopel2dict)) #使用定义的转化函数
print(p.__dict__)
print(json.dumps(p, default=lambda obj: obj.__dict__)) #也可以直接使用实例自带的__dict__属性
#反序列化
json_str = json.dumps(p, default=peopel2dict)
print(json.loads(json_str, object_hook=dict2people))


#输出
<__main__.People object at 0x1083f4550>
{"name": "Tom", "age": 18}
{'name': 'Tom', 'age': 18}
{"name": "Tom", "age": 18}
<__main__.People object at 0x10840c3d0>