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操作会有inputoutput两个数据流,但是也有使用一个的情况,例如:

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

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

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

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

    1. 同步IO:在程序遇到需要把数据读入磁盘时,程序暂停执行后续代码,等待数据读入磁盘后,再执行剩余代码,在数据读入途中CPU进行等待
    2. 异步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,常用作临时缓冲
  • 如果要想在内存中对数据进行读写,可以使用StringIOBytesIO,前者是对字符串数据的读写,后者是对二进制数据的读写

- 二进制文件

  • 如果读取的是二进制文件,我们可以使用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与直接读写文件类似,有相同的接口

四、操作文件和目录

  • 当我们要操作文件、目录时,可以在命令行下面输入操作系统提供的各种命令来完成,例如dircp命令等

  • 如果想要在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' 
    
    

- 操作文件和目录