Haste makes waste

Python-Liao-07-IO-文件读写-序列化

Posted on By lijun

[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。

54. IO编程

IO在计算机中指Input/Output即输入输出,涉及到数据交换的地方,通常是磁盘或网络,就需要IO接口。

IO编程中,Stream流很重要,Input Stream就是数据从外面(磁盘,网络)流进内存,Output Stream就是数据从内存流到外面去。

由于CPU和内存的速度远高于外设速度,所以IO编程中就存在两种模式,同步IO和异步IO。

  • 同步IO:IO如将100M数据写入磁盘完毕后,再接下去执行其他部分。
  • 异步IO:将100M数据写入磁盘的命令传达后,立即执行其他部分

同步和异步的区别在于是否等待IO执行的结果,使用异步IO来编写程序,性能会远高于同步IO,但是异步IO的缺点是编程模型复杂。 异步IO编程中,有如下两种方式确认IO是否完成:

  • 回调模式:磁盘写入完毕后,执行IO的程序过来通知你(当前程序)
  • 轮询模式:执行IO的程序发完毕的消息,当前程序不停的确认该消息

总之,异步IO的复杂度远远高于同步IO。

55. 文件读写

Python 内置的 open()函数, 传入文件名和标示符:f = open("c:\\test\\1.log","r")

调用 read()方法可以一次读取文件的全部 内容,Python 把内容读到内存,用一个 str 对象表示: f.read()

输出如下:

'just a test1\njust a test2\njust a test3'

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

但是上面存在一个问题,即如果IO出现error,后面的f.close不会被执行,所以需要添加异常处理:

try: 
    f = open('c:\\test\\1.log', 'r') 
    print(f.read()) 
finally: 
    if f: 
        f.close()

但是每次都按照上面的方式写太繁琐了,python中可以简化为:

with  open('c:\\test\\1.log', 'r') as f:
    print(f.read())
  • 如下是返回第一行
with  open('c:\\test\\1.log', 'r') as f:
    print(f.readline())
  • 如下返回复数行的一个list:
with  open('c:\\test\\1.log', 'r') as f:
    print(f.readlines())

输出如下:

['just a test1\n', 'just a test2\n', 'just a test3']
  • 如下是分隔之后返回:
with  open('c:\\test\\1.log', 'r') as f:
    for l in f.readlines():
        print(l.strip())

输出如下

just a test1
just a test2
just a test3

1. file-like object

在前面讲过类似的file-like object,传统的面向对象语言中,要实现多态功能,就需要从特定的类下面继承,但是python是动态语言,只需要这个类实现了read方法就行,所以,内存流网络流或自定义流等都是file-like object。

另外,StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

2. 二进制文件

对于非文件形式文件,如图片,使用rb形式读取,最后输出为一连串的二进制码。

f = open('C:\\test\\1.png', 'rb') 
f.read() 

3. 字符编码

非UTF-8编码的文本文件时:

f = open('C:\\test\\1.txt', 'r', encoding='utf-8') 
f.read()

输出如下(文本文件中只写入了连接二字):

'\ufeff连接'

遇到有些编码不规范的文件, 你可能会遇到 UnicodeDecodeError, 因为在 文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函 数还接收一个 errors 参数,表示如果遇到编码错误后如何处理。最简单 的方式是直接忽略:

f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore') 

4. 写文件

f = open('C:\\test\\2.txt', 'w') 
f.write('Hello, world!') 
f.close() 

当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是 放到内存缓存起来,空闲的时候再慢慢写入 只有调用 close()方法时, 操作系统才保证把没有写入的数据全部写入磁盘。

忘记调用 close()的 后果是数据可能只写了一部分到磁盘, 剩下的丢失了。 所以, 还是用 with 语句来得保险:

with open('C:\\Users\\61041150\\Pictures\\3.txt', 'w') as f: 
     f.write('Hello, world!') 

with 语句操作文件 IO 是个好习惯。

56. StringIO和BytesIO

1. StringIO

StringIO 顾名思义就是在内存中读写 str。 StringIO 顾名思义就是在内存中读写 str。

from io import StringIO
f = StringIO()
f.write('hello')
f.write(' ')
f.write('world!')

输出为6,因为最后写入的world!是6个字符。

print(f.getvalue()),时会输出hello world!,即上面写入到IO中的字符串。

要读取StringIO,可以用一个 str 初始化 StringIO,然后像读文件一样读取:

f = StringIO('Hello!\nHi!\nGoodbye!') 
while True:
    s = f.readline()
    if s == '':
        break
    print(s.strip())

最后输出:

Hello!
Hi!
Goodbye!

2. BytesIO

BytesIO 实现了在内存中读写 bytes。

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

输出b'\xe4\xb8\xad\xe6\x96\x87'

StringIO 和 BytesIO 是在内存中操作 str 和 bytes 的方法,使得和读写文 件具有一致的接口。

57. 操作文件和目录

代码 意义 其他
import os 导入os包  
os.name 操作系统类型  
os.uname() 此函数在windows下不可用 系统详细信息
os.environ 环境变量  
os.environ.get(‘PATH’) 要获取某个环境变量的值,可以调用 os.environ.get(‘key’)  
os.environ.get(“lijun-key”,”default-value”) 找不到对应key时,用default-value返回  
os.path.abspath(“.”) 获取当前路径的绝对路径  

下面的代码,新建一个新的文件夹,删除一个文件夹。 在生成新的文件路径的时候,不要自己进行字符串组合,使用path.join能够排除操作系统的差异,比如windows和linux的分隔符就是不同的。

# 组合一个新的路径名
newpath = os.path.join("C:\\test","directoryTest")
os.mkdir(newpath) # 新建一个目录
os.rmdir(newpath) # 删除一个目录

split和splittext,用于拆分路径名,生成一个tuple即元祖,效果如下:

newpathfile = os.path.join(newpath,"testtest.txt")
os.path.split(newpathfile)

输出 ('C:\\test\\directoryTest', 'testtest.txt')

# 最后部分是文件的扩展名
os.path.splitext(newpathfile)

输出 ('C:\\test\\directoryTest\\testtest', '.txt')

  • 重命名文件和删除文件:
# 重命名文件
os.rename("1227.txt","1227-modified.txt")

# 删除指定文件
os.remove("1.txt")

shutil模块提供了很多如copyfile的函数,是os的很好补充,需要是查阅。 列举当前路径下的文件夹名:

[x for x in os.listdir('.') if os.path.isdir(x)] 

列举当前路径下的所有py文件:

[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] 

58. 序列化

d = dict(name='Bob', age=20, score=88) ,我们把变量从内存中变成可存储或传输的过程称之为序列化、序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到 别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化, 即 unpickling。

import pickle
d = dict(name='Bob', age=20, score=88) 
pickle.dumps(d)

输出如下:

b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x03\x00\x00\x00Bobq\x02X\x05\x00\x00\x00scoreq\x03KXX\x03\x00\x00\x00ageq\x04K\x14u.'
  • 将上面的对象d序列化到文件:
f = open('dump.txt', 'wb') 
pickle.dump(d, f) 
f.close()
  • 从序列化文件中还原对象
f = open('dump.txt', 'rb') 
d = pickle.load(f)
f.close()
d

输出 {'age': 20, 'name': 'Bob', 'score': 88}

1. jason文件序列化

import json
d = dict(name='Bob', age=20, score=88) 
json.dumps(d) 

输出:'{"name": "Bob", "age": 20, "score": 88}'

json_str = '{"age": 20, "score": 88, "name": "Bob"}' 
json.loads(json_str) 

输出:{'age': 20, 'name': 'Bob', 'score': 88}

2. JSON进阶

将一个class对象序列化为JSON对象. python特定的序列化模块是pickle,但是如果想把序列化实现得更web通用化,最好使用json进行序列化。

import json
class Student(object):
    def __init__(self,name,age,score):
        self.name = name
        self.age = age
        self.score = score
    
    def student2dict(std):
        return {
            "name":std.name,
            "age":std.age,
            "score":std.score
        }

    def dict2student(d):
        return Student(d['name'],d['age'],d['score'])
        
s = Student("Bol",20,88)
print(json.dumps(s, default=lambda obj: obj.__dict__)) 

输出{"name": "Bol", "age": 20, "score": 88}