一、IO的概念
IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。
IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。
IO又分为同步IO和异步IO:
(1)同步IO:好比你去麦当劳点餐,你说“来个汉堡”,服务员告诉你,对不起,汉堡要现做,需要等5分钟,于是你站在收银台前面等了5分钟,拿到汉堡再去逛商场,这是同步IO。
(2)异步IO:你说“来个汉堡”,服务员告诉你,汉堡需要等5分钟,你可以先去逛商场,等做好了,我们再通知你,这样你可以立刻去干别的事情(逛商场),这是异步IO。
二、文件读写
1.读文件
通过Python内置的open()函数打开一个文件,read()函数读取文件的全部内容,close()函数关闭文件。
>>> f=open('/Users/michael/test.txt', 'r') #'r'表示只读
>>> f.read() #读取文件
'Hello,world!'
>>> f.close() #关闭文件
要打开的文件如果不存在,会抛出一个IOError错误:
>>> f=open('/Users/michael/notfound.txt','r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt
但是每次都这么写实在太繁琐,Python引入了with语句来自动帮我们调用close()方法:
with open('/path/to/file','r') as f:
print(f.read())
Python提供了以下读函数:
函数 | 含义 |
---|---|
read() | 一次性读取文件的全部内容 |
read(size) | 每次读取size个字节的内容 |
readline() | 每次读取一行内容 |
readlines() | 一次读取所有内容并按行返回list |
2.file-like Object
像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。
StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
3.二进制文件
前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'
模式打开文件即可:
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
4.字符编码
要读取非UTF-8编码的文本文件,需要给open()
函数传入encoding
参数,例如,读取GBK编码的文件:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'
5.写文件
写文件和读文件是一样的,唯一区别是调用open()
函数时,传入标识符'w'
或者'wb'
表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用write()
来写入文件,但是务必要调用f.close()
来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
要写入特定编码的文本文件,请给open()
函数传入encoding
参数,将字符串自动转换成指定编码。
细心的童鞋会发现,以'w'
模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入'a'
以追加(append)模式写入。
所有模式的定义及含义可以参考Python的官方文档。
三、StringIO和BytesIO
1.StringIO
很多时候,数据读写不一定是文件,也可以在内存中读写。
StringIO顾名思义就是在内存中读写str。
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
getvalue()
方法用于获得写入后的str。
要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
2.BytesIO
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
请注意,写入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'
四、OS模块:环境变量、文件和目录
1.查看操作系统类型
>>> import os
>>> os.name # 操作系统类型
'nt'
如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
2.环境变量
在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:
>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})
要获取某个环境变量的值,可以调用os.environ.get('key'):
>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'
3.操作文件和目录
操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,shutil模块对os模块进行补充。
查看、创建和删除目录的调用如下:
函数调用 | 结果 | 含义 |
---|---|---|
os.path.abspath('.') | ‘/Users/michael’ | 查看当前目录的绝对路径 |
os.path.join('/Users/michael', 'testdir') | '/Users/michael/testdir' | 将新目录的路径表示出来 |
os.mkdir('/Users/michael/testdir') | 无 | 创建一个新目录 |
os.rmdir('/Users/michael/testdir') | 无 | 删除一个目录 |
目录路径的操作函数如下(只是字符串的操作,与目录无关):
含义 | 操作系统 | 函数 | 结果 |
---|---|---|---|
合并路径 | Linux/Unix/Mac | os.path.join('/Users/michael', 'testdir') | '/Users/michael/testdir' |
Windows | os.path.join('C:\Users\michael', 'testdir') | 'C:\Users\michael\testdir' | |
拆分路径 | Linux/Unix/Mac | os.path.split('/Users/michael/testdir/file.txt') | ('/Users/michael/testdir', 'file.txt') |
Windows | os.path.split('C:\Users\michael\testdir\file.txt') | ('C:\Users\michael\testdir', 'file.txt') | |
获得扩展名 | Linux/Unix/Mac | os.path.splitext('/Users/michael/testdir/file.txt') | ('/Users/michael/testdir/file', '.txt') |
Windows | os.path.splitext('C:\Users\michael\testdir\file.txt') | ('C:\Users\michael\testdir\file', '.txt') |
对文件操作的函数调用如下:
函数调用 | 含义 |
---|---|
os.rename('test.txt', 'test.py') | 对文件重命名 |
os.remove('test.py') | 删除文件 |
shutil.copy(src, dst) | 将src复制到dst,src和dst可以是文件名或目录名 |
shutil.copyfile(src, dst) | 将src复制到dst,src和dst只能是文件名 |
利用Python的特性来过滤文件:
(1)列出当前目录下的所有目录:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
(2)列出所有.py文件:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
五、序列化
1.序列化的定义:序列化就是把变量从内存变成可存储或传输的过程,即pickling。
反序列化的定义:把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
2.pickle模块:
dumps()函数和loads()函数:将变量序列化为一个bytes
>>> import pickle
>>> d = dict(name='Bob', age=20, score=80) #创建一个dict
>>> dd = pickle.dumps(d) #将d序列化为一个bytes,即dd,可以将dd写入文件中
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
>>> dl = pickle.loads(dd) #将dd反序列化为dl,即dict变量
{'age':20, 'score':88, 'name':'Bob'}
dump()函数和load()函数:直接把对象序列化后写入一个file-like Object
#序列化
>>> f = open('dump.txt', 'wb') #创建一个文件
>>> pickle.dump(d, f) #将d序列化后存入f文件中
>>> f.close() #关闭文件
#反序列化
>>> f = open('dump.txt', 'rb') #打开dump.txt
>>> d = pickle.load(f) #将f反序列化
>>> f.close() #关闭文件
>>> d #获得dict
{'age': 20, 'score': 88, 'name': 'Bob'}
3.json模块:
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
JSON表示的对象就是标准的JavaScript语言的对象,JSON和Python内置的数据类型对应如下:
JSON类型 | Python类型 |
---|---|
{} | dict |
[] | list |
"string" | str |
1234.56 | int或float |
true/false | True/False |
null | None |
json模块也有dumps()、loads()、dump()、load()等函数,用法与pickle模块类似。
将Python对象序列化为一个JSON:
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age":20, "score":80, "name":"Bob"}'
将JSON反序列化为一个Python对象:
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
{'age': 20, 'score': 88, 'name': 'Bob'}
4.json进阶
将一个class对象序列化为JSON:
>>> import json
>>> class Student(object): #定义一个Student类
... def __init__(self, name, age, score):
... self.name = name
... self.age = age
... self.score = score
...
>>> def student2dict(std): #定义Student转dict的函数
... return {
... 'name' : std.name,
... 'age' : std.age,
... 'score' : std.score
... }
...
>>> s = Student('Bob', 20, 88) #实例化Student
>>> print(json.dumps(s, default = student2dict)) #将转换函数传给可选参数default
{"age":20, "name":"Bob", "score":88}
>>> print(json.dumps(s,default = lambda obj:obj.__dict__)) #因为通常class的实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。也有少数例外,比如定义了__slots__的class
{"age":20, "name":"Bob", "score":88}
将JSON反序列化为一个class对象:
>>> def dict2student(d): #定义dict转student的函数
... return Student(d['name'], d['age'], d['score'])
...
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}' #student类的JSON对象
>>> print(json.loads(json_str, object_hook=dict2student)) #将转换函数传给object_hook可选参数
<__main__.Student object at 0x10cd3c190>