Python学习(九)—— IO编程

一、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,说明系统是LinuxUnixMac 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/Macos.path.join('/Users/michael', 'testdir')

'/Users/michael/testdir'

Windowsos.path.join('C:\Users\michael', 'testdir')'C:\Users\michael\testdir'
拆分路径Linux/Unix/Macos.path.split('/Users/michael/testdir/file.txt')('/Users/michael/testdir', 'file.txt')
Windowsos.path.split('C:\Users\michael\testdir\file.txt')('C:\Users\michael\testdir', 'file.txt')
获得扩展名Linux/Unix/Macos.path.splitext('/Users/michael/testdir/file.txt')('/Users/michael/testdir/file', '.txt')
Windowsos.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.56int或float
true/falseTrue/False
nullNone

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>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值