Python(11)IO编程(上)

简介: Python(11)IO编程(上)

一、IO编程概述


  • 在计算机中,IO指的是input(输入)output(输出)
  • 计算机中程序和运行时产生的数据会在内存中驻留,然后由CPU来执行,其中涉及到数据交换的地方,例如磁盘、网络等,就需要IO接口,下面来看一些日常生活、工作中的案例,来帮助更好的理解IO:


在我们打开浏览器时,访问百度首页,这时浏览器这个程序就需要通过网络IO获取百度的首页,浏览器会先发送数据给百度服务器,告诉服务器想要的HTML页面,这个动作是往外发数据,叫做output,随后百度服务器把网页发过来,这个动作是从外面接收数据,叫做input。


通常来说,程序完成IO操作会有input和output两个数据流,但是也有使用一个的情况,例如:


从磁盘读取文件到内存,只有input操作,从内存把数据写到磁盘,同样只有output操作


在IO编程中,Stream(流)是一个很重要的概念,流就像是一个水管,而数据就是水管里的水,水管里的水只能单向流动,其中inputstream就像是水从B端流向A端,即数据从磁盘读进内存,反之,outputstream就像是水从A端流向B端,即内存把数据写到磁盘,对于浏览网页来说,浏览器和服务器之间就需要至少建立两条水管,来实现输入和输出的目的


由于CPU和内存的速度远远高于外设的速度,所以在IO编程中,就会出现速度严重不匹配的问题,例如:


当我们想把一个100MB的数据写入磁盘时,如果是CPU的话,可能只需要0.01秒,而磁盘接收这个数据,可能就要10秒,这样的问题有两种解决方法:


同步IO:在程序遇到需要把数据读入磁盘时,程序暂停执行后续代码,等待数据读入磁盘后,再执行剩余代码,在数据读入途中CPU进行等待

异步IO:在遇到上面的情况时,CPU不进行等待,后续代码继续执行

上面的两种模式的区别在于,CPU是否等待IO执行的结果,下面来看一下案例,帮助更好的理解两种模式:


背景:小明想去逛商场,但是逛商场之前想吃汉堡


同步IO:小明去点汉堡之后,服务员说,汉堡现做,需要等待5分钟,小明点餐之后,在收银台前等了5分钟,拿到汉堡之后,再去逛商场,这就是同步IO

异步IO:小明去点汉堡之后,服务员说,汉堡现做,需要5分钟,汉堡做好之后通知小明,在这期间,小明可以先去逛商场,这就是异步IO

看过上面的例子之后,很明显的看出,异步IO比同步IO速度快,使用异步IO来编写程序的性能远远高于同步IO,但是异步IO的缺点就是编程模型复杂,根据上面的案例中,我们需要定义在汉堡做好之后,服务员要通过什么方式通知小明,如果是服务员跑过来找到小明说的话,这是回调模式,如果服务员发短信通知小明,小明需要不定期的查看手机短信,这就是轮询模式


操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也一样,下面只说同步IO模式


二、文件读写


  • 读写文件是最常见的IO操作,Python内置了读写文件的函数,用法和C语言是兼容的
  • 在读写文件之前,我们需要了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象,通常把这个文件对象叫做文件描述符,然后通过操作系统提供的接口从这个文件对象中读取数据,即读取文件,或者把数据写入这个文件对象,即写入文件


- 读取文件

要以读取文件的模式打开一个文件对象,可以使用Python内置的函数open(),例如:

#下面以centos系统为例
[root@centos-1 ~]# echo "aaaaa" > test.txt  #先创建一个文件
[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f = open("/root/test.txt","r")     
>>> 
- open函数的第一个参数是文件路径,第二个参数为标识符,'r'为读
#如果打开的文件对象不存在,则会抛出一个IOError的错误,并且给出错误码和详细的信息,说明文件不存在
>>> f = open("/root/aaa.txt","r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '/root/aaa.txt'
- 文件成功打开后,调用'read()'方法可以一次性读取文件的全部内容,Python会把内容读取到内存,使用'str'字符串对象表示
>>> f = open("/root/test.txt","r")
>>> f.read()
'aaaaa\n'   #\n表示换行符
- 在文件使用完毕之后,我们必须使用'close()'方法来关闭文件,因为文件对象会占用操作系统资源,并且操作系统同一时间能打开的文件数量也是有限的
>>> f.close()
- 由于文件读写时可能会产生'IOError'错误,一旦出错后,后面的'f.close()'方法就不会调用,所以,为了保证无论是否出错都能正确的关闭文件,我们可以使用'try...finally'来达到目的
[root@centos-1 ~]# cat test.py 
#/usr/bin/enc python3
# -*- coding: utf-8 -*-
try:
    f = open('/root/test.txt','r')
    print(f.read())
finally:
    if f:
        f.close()
[root@centos-1 ~]# python3 test.py   #执行脚本
aaaaa
- 但是每次都像上面这么写的话,实在是太麻烦了,Python引入了'with'语句来自动帮我们调用'close()'方法
[root@centos-1 ~]# cat test.py 
#/usr/bin/enc python3
# -*- coding: utf-8 -*-
with open('/root/test.txt','r') as f:
    print(f.read())
[root@centos-1 ~]# python3 test.py 
aaaaa

调用read()方法会一次性读取文件的所有内容,如果文件太大的话,内存就爆了,所以,保险起见,我们可以反复调用read(size)方法,每次最多读取指定size大小的内容


除了read(size),调用readline()可以每次读取一行内容,调用readlines()一次性读取所有内容并按行返回list列表,根据需求决定如何调用


如果文件很小,直接使用read()一次性读取是最方便的,如果不能确定文件大小,我们可以反复调用read(size),如果是配置文件,调用readlines()最方便

[root@centos-1 ~]# cat test.py 
#/usr/bin/enc python3
# -*- coding: utf-8 -*-
f = open('test.txt','r')
n = 1
for line in f.readlines():
  print('第%s行为:' % n,line.strip())  #line.strip()会去掉每行的空格、换行符、制表符等
  n = n + 1
f.close()
[root@centos-1 ~]# python3 test.py 
第1行为: aaaaa
第2行为: bbbbb
第3行为: ccccc

- file-like Object


像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object对象,除了file之外,还可以是内存的字节流、网络流、自定义流等

file-like Object不要求从特定类继承,只要写个read()方法就行

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

如果要想在内存中对数据进行读写,可以使用StringIO和BytesIO,前者是对字符串数据的读写,后者是对二进制数据的读写


- 二进制文件


  • 如果读取的是二进制文件,我们可以使用rb模式打开文件,例如:


>>> f = open('/root/test.txt', 'rb')    #使用rb模式
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

- 字符编码


  • 想要读取非utf-8编码的文本文件,需要给open()函数传入encoding参数,例如:


- 读取GBK编码的文件
>>> f = open('/root/test.txt', 'r', encoding='gbk')
>>> f.read()
'测试'


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


>>> f = open('/root/test.txt', 'r', encoding='gbk', errors='ignore')

- 写入文件


  • 写文件和读文件是一样的,区别是在调用open()函数时,传入标识符wwb表示写入文本文件或者二进制文件
[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f = open('/root/test.txt','w')
>>> f.write('aaaaaaaa')
8
>>> f.read()   #因为标识符是w,写入,所以无法读取文件
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: not readable
>>> f.close()
>>> f = open('/root/test.txt','r')
>>> f.read()
'aaaaaaaa'

可以反复调用write()方法来写入文件,但是和读取文件一样,在写入之后必须要使用close()方法来关闭文件,当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再去写入,只有调用close()方法时,操作系统才会保证把没有写入的数据全部写入磁盘


忘记调用close()方法的后果,就是写入的数据可能只有一点到了磁盘,其余数据全部丢失,所以,通常来说,为了防止忘记使用close(),我们可以直接使用with语句

[root@centos-1 ~]# cat test.txt 
aaaaaaaa
[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> with open('/root/test.txt','w') as f:
...     f.write('Hello World!!!')
... 
14
>>> with open('/root/test.txt','r') as f:
...     print(f.read())
... 
Hello World!!!

要写入特定编码的文本文件,需要给open()函数传入encoding参数,将字符串自动转换成指定编码


从上面的案例可以看到,test.txt文件原本的内容是aaaaaaaa,在以w模式写入文件时,新的Hello World!!!的内容覆盖了原本的内容,如果我们想要追加到文件末尾的话,可以使用a模式,以追加模式写入

[root@centos-1 ~]# cat test.txt 
Hello World!!![root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> with open('/root/test.txt','a') as f:
...     f.write('aaaaaaa')
... 
7
>>> with open('/root/test.txt','r') as f:
...     print(f.read())
... 
Hello World!!!aaaaaaa

三、StringIO和BytesIO


- StringIO


很多时候,数据读写不一定是文件,也可以在内存中读写,当并不想把数据写到本地磁盘时,就可以使用StringIO


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


想要把str写入StringIO,首先需要创建一个StringIO,然后像文件一样写入即可,例如:

[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('Hello')    #直接进行写入
5
>>> f.write(' World!!!')
9
>>> print(f.getvalue())  #使用getvalue()方法获取写入后的str
Hello World!!!

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

[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from io import StringIO
>>> f = StringIO('Hello! \n World!!!')
>>> while True:
...     s = f.readline()
...     if s == '':
...             break
...     print(s.strip())
... 
Hello!
World!!!

- BytesIO


  • 上面的StringIO操作的只能是Str数据,如果要操作二进制数据,就需要使用BytesIO
  • BytesIO实现了在内存中读写bytes,下面来创建一个BytesIO,然后写入一些bytes
[root@centos-1 ~]# python3
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
- 注意上面写入的是经过UTF-8编码的bytes

和StringIO相似,可以使用一个bytes初始化BytesIO,然后像文件一样读取

[root@centos-1 ~]# python
Python 3.9.9 (main, May 10 2022, 15:32:30) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'
  • 小结:StringIO和BytesIO是在内存中操作str和bytes的方法,从内存中读写string或者bytes与直接读写文件类似,有相同的接口
目录
相关文章
|
15天前
|
存储 人工智能 数据处理
Python:编程的艺术与科学的完美交融
Python:编程的艺术与科学的完美交融
19 1
|
13天前
|
调度 数据库 Python
【专栏】异步IO在处理IO密集型任务中的高效性
【4月更文挑战第27天】本文介绍了Python并发编程和异步IO,包括并发的基本概念(多线程、多进程、协程),线程与进程的实现(threading和multiprocessing模块),协程的使用(asyncio模块),以及异步IO的原理和优势。强调了异步IO在处理IO密集型任务中的高效性,指出应根据任务类型选择合适的并发技术。
|
2天前
|
JSON 数据格式 开发者
pip和requests在Python编程中各自扮演着不同的角色
`pip`是Python的包管理器,用于安装、升级和管理PyPI上的包;`requests`是一个HTTP库,简化了HTTP通信,支持各种HTTP请求类型及数据交互。两者在Python环境中分别负责包管理和网络请求。
13 5
|
4天前
|
存储 Python 容器
Python高级编程
Python集合包括可变的set和不可变的frozenset,用于存储无序、不重复的哈希元素。创建集合可使用{}或set(),如`my_set = {1, 2, 3, 4, 5}`。通过add()添加元素,remove()或discard()删除元素,如`my_set.remove(3)`。
|
5天前
|
测试技术 Python
Python模块化方式编程实践
Python模块化编程提升代码质量,包括:定义专注单一任务的模块;使用`import`导入模块;封装函数和类,明确命名便于重用;避免全局变量降低耦合;使用文档字符串增强可读性;为每个模块写单元测试确保正确性;重用模块作为库;定期维护更新以适应Python新版本。遵循这些实践,可提高代码可读性、重用性和可维护性。
25 2
|
11天前
|
测试技术 调度 索引
python编程中常见的问题
【4月更文挑战第23天】
31 2
|
11天前
|
网络协议 算法 网络架构
Python网络编程之udp编程、黏包以及解决方案、tcpserver
Python网络编程之udp编程、黏包以及解决方案、tcpserver
|
12天前
|
机器学习/深度学习 数据挖掘 算法框架/工具
Python:编程的艺术与魅力
Python:编程的艺术与魅力
24 3
|
12天前
|
机器学习/深度学习 数据可视化 数据挖掘
实用技巧:提高 Python 编程效率的五个方法
本文介绍了五个提高 Python 编程效率的实用技巧,包括使用虚拟环境管理依赖、掌握列表推导式、使用生成器提升性能、利用装饰器简化代码结构以及使用 Jupyter Notebook 进行交互式开发。通过掌握这些技巧,可以让你的 Python 编程更加高效。
|
12天前
|
并行计算 数据处理 开发者
Python并发编程:解析异步IO与多线程
本文探讨了Python中的并发编程技术,着重比较了异步IO和多线程两种常见的并发模型。通过详细分析它们的特点、优劣势以及适用场景,帮助读者更好地理解并选择适合自己项目需求的并发编程方式。