Python:IO编程
同步IO与异步IO:
由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:
-
同步IO:CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;好比你去麦当劳点餐,你说“来个汉堡”,服务员告诉你,对不起,汉堡要现做,需要等5分钟,于是你站在收银台前面等了5分钟,拿到汉堡再去逛商场,这是同步IO。
-
异步IO:CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。
这里只讲同步IO,至于异步IO需要学到服务器再说,因为比较复杂。
文件读写
Python内置了读写文件的函数,用法和C是兼容的。具体基本用法如下:
# 读文件第一中写法,并且可以忽略编码错误: errors='ignore'
try:
f = open('C:/Users/Administrator/Desktop/test.txt', 'r', encoding='gbk', errors='ignore')
print(f.read())
finally:
if f:
f.close()
# 读文件第二种写法,推荐,这种自动 f.close(),其实就是上面的简写
with open('C:/Users/Administrator/Desktop/test.txt', 'r', encoding='gbk') as f:
# 以下方法会一次性读取全部内容,适合内容少的,如果是内容多的情况可以反复调用read(size)方法,
# 调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list,这个适合配置文件
print(f.read())
# 用'rb'模式打开文件是二进制
with open('C:/Users/Administrator/Desktop/test.txt', 'rb') as f:
print(f.read())
# 写文件,当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,
# 操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。
with open('C:/Users/Administrator/Desktop/test.txt', 'w', encoding='gbk') as f:
f.write('Hello, world!')
# 如果我们希望追加到文件末尾怎么办?可以传入'a'以追加(append)模式写入
with open('C:/Users/Administrator/Desktop/test.txt', 'a', encoding='gbk') as f:
f.write('我是新添加的')
StringIO和BytesIO
如何在内存中进行读写数据?那么就可以用StringIO和BytesIO:
# StringIo
from io import StringIO
# 写入
f = StringIO()
f.write('明明很帅')
# f.getvalue()可以获取
print(f.getvalue())
# 读取数据
f = StringIO('Hello!\nHi!\nGoodbye!')
while True:
s = f.readline()
if s == '':
break
print(s.strip()) # 把末尾的'\n'删掉
# BytesIO,用于操作二进制数据
from io import BytesIO
# 写入
f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
# 读取数据
print(f.read())
f.write("啦啦啦".encode('utf-8'))
# 取数据
print(f.getvalue())
系统文件目录的操作
如果要在Python程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python内置的os模块也可以直接调用操作系统提供的接口函数。具体用法如下(只是其中一小部分):
import os
# 操作系统类型,如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
print(os.name)
# 要获取详细的系统信息,可以调用uname()函数,但是在Windows上不提供,所以调用以下代码会报错
# print(os.uname())
# 获取操作系统全部的环境变量
print(os.environ)
# 要获取某个环境变量的值,可以调用os.environ.get('key')
print(os.environ.get('PATH'))
# 查看当前目录的绝对路径
print(os.path.abspath('.'))
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来,千万不要自己去拼凑,很容易出错的
print(os.path.join('F:/Pythonproject/test_dir','dir'))
# 同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
print(os.path.split('F:/Pythonproject/test_dir'))
# 然后创建一个目录:
os.mkdir('F:/Pythonproject/test_dir/dir')
# 删除这个目录
os.rmdir('F:/Pythonproject/test_dir/dir')
# os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便
print(os.path.splitext('F:/Pythonproject/test_dir/_init_.py'))
# 另外还有对文件重命名: os.rename(),删掉文件:os.remove('test.py')
# 列出当前目录下的所有目录
print([x for x in os.listdir('.') if os.path.isdir(x)])
# 要列出所有的.py文件
print([x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'])
序列化
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。以下代码将演示如何讲一个变量,一个json,一个对象如何序列化和反序列化:
import pickle
# 变量序列化
d = dict(name='Bob', age=20, score=88)
# 将序列化的写入文件
try:
f = open('C:/Users/Administrator/Desktop/test.txt', 'wb')
# pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。
# 或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object
# dumps = pickle.dumps(d)
dump = pickle.dump(d, f)
finally:
f.close()
# 变量反序列化
try:
f = open('C:/Users/Administrator/Desktop/test.txt', 'rb')
# 可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()
# 方法从一个file-like Object中直接反序列化出对象。
load = pickle.load(f)
print(load)
finally:
f.close()
# 将变量序列化成json
import json
d = dict(name='Bob', age=20, score=88)
# dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。
dumps = json.dumps(d)
print(dumps)
# 要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:
print(json.loads(dumps))
# 将自定义对象进行序列化
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
# 将对象转换成dict的转换函数
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
s = Student('Bob', 20, 88)
# 可选参数default就是把任意一个对象变成一个可序列为JSON的对象,我们只需要为Student专门写一个转换函数,再把函数传进去即可
print(json.dumps(s, default=student2dict))
# 下次如果遇到一个Teacher类的实例,照样无法序列化为JSON。我们可以偷个懒,把任意class的实例变为dict
# 因为通常class的实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。也有少数例外,比如定义了__slots__的class。
json_dumps = json.dumps(s, default=lambda obj: obj.__dict__)
print(json_dumps)
# 同样的道理,如果我们要把JSON反序列化为一个Student对象实例,loads()方法首先转换出一个dict对象,
# 然后,我们传入的object_hook函数负责把dict转换为Student实例
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
print(json.loads(json_dumps, object_hook=dict2student))