Python(11)IO编程
Posted 礁之
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python(11)IO编程相关的知识,希望对你有一定的参考价值。
文章目录
本文参考廖雪峰的官网:IO编程 - 廖雪峰的官方网站 (liaoxuefeng.com)
一、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()
函数时,传入标识符w
或wb
表示写入文本文件或者二进制文件[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与直接读写文件类似,有相同的接口
四、操作文件和目录
-
当我们要操作文件、目录时,可以在命令行下面输入操作系统提供的各种命令来完成,例如
dir
,cp
命令等 -
如果想要在Python程序中执行这些目录和文件的操作怎么办,其实
操作系统提供的命令只是简单的调用了操作系统提供的接口函数
,而Python内置的os
模块也可以直接调用操作系统提供的接口函数 -
打开Python交互式命令行,来看一下
os
模块的基本功能:[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. >>> import os >>> os.name 'posix' #如果是posix,说明系统是linux,unix,mac,如果是nt,则说明是windows >>> os.uname() #使用uname()函数,可以获取更详细的系统信息 posix.uname_result(sysname='Linux', nodename='centos-1', release='3.10.0-693.el7.x86_64', version='#1 SMP Tue Aug 22 21:09:27 UTC 2017', machine='x86_64')
-
在windows上打开Python交互式命令行,执行与centos相同的命令:
PS D:\\工作\\work> python Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.name 'nt' >>> os.uname() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'os' has no attribute 'uname'. Did you mean: 'name'? #可以发现,在windows中,uanme()函数无法使用,也就是说os模块的某些函数是与操作系统挂钩的
- 环境变量
-
在操作系统中定义的环境变量,全部保存在
os.environ
这个变量中,可以直接查看>>> os.environ environ('XDG_SESSION_ID': '2', 'HOSTNAME': 'centos-1', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'HISTSIZE': '1000', 'SSH_CLIENT': '10.10.30.107 55294 22', 'SSH_TTY': '/dev/pts/0', 'USER': 'root', 'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:', 'MAIL': '/var/spool/mail/root', 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin', 'PWD': '/root', 'LANG': 'zh_CN.UTF-8', 'HISTCONTROL': 'ignoredups', 'SHLVL': '1', 'HOME': '/root', 'LOGNAME': 'root', 'SSH_CONNECTION': '10.10.30.107 55294 10.10.30.69 22', 'LESSOPEN': '||/usr/bin/lesspipe.sh %s', 'XDG_RUNTIME_DIR': '/run/user/0', '_': '/usr/local/bin/python3.9') - 需要获取某个环境变量的值,可以调用'os.environ.get('key')' >>> os.environ.get('PATH') '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin' >>> os.environ.get('x','default') #当x变量不存在时,返回默认值default 'default' >>> os.environ.get('a','aaaaa') #当a变量不存在时,返回默认值aaaaa 'aaaaa'
- 操作文件和目录
-
操作文件和目录的函数一部分放在
os
模以上是关于Python(11)IO编程的主要内容,如果未能解决你的问题,请参考以下文章