IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。

  • 存在问题:输入和接收速度不匹配
  • 同步IO:CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行(好了叫我)。
  • 异步IO:CPU不等待,只是告诉磁盘,慢慢写,不着急,接着干别的事去了,于是,后续代码可以立刻接着执行(好了没好了没)

同步和异步的区别就在于是否等待IO执行的结果

文件读写

读文件

要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符,标示符'r'表示读:

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py','r')
print(f.read())
f.close()

如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在,文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容。最后一步是调用close()法关闭文件,文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现。

try:
    f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py', 'r')
    print(f.read())
finally:
    if f:
        f.close()

这么写太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

# with
with open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py') as f:
    print(f.read())

和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。
调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

with open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py') as f:
    # readlines()
    for line in f.readlines():
        print(line.strip())

  • file-like:在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
  • 二进制文件:读取二进制文件,比如图片、视频等等,用'rb'模式打开文件:
f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/wallpaper_5242054.jpg','rb')
f.read()
  • 字符编码:要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/gbk.txt', 'r', encoding='gbk')
f.read()

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

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/gbk.txt', 'r', encoding='gbk',errors='ignore')

写文件

写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w''wb'表示写文本文件或写二进制文件:

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt', 'w')
f.write('hello world!!!')
f.close()

可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:

with open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt', 'w') as f:
    f.write('hi python!!!')

要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。'w’模式写入文件时,如果文件已存在,会直接覆盖,传入'a'以追加(append)模式写入。
在这里插入图片描述

(二)StringIO和BytesIO

StringIO

StringIO:内存中读写str

from io import StringIO

# 内存写入
f = StringIO()
print(f.write('hello'))
print(f.write(' '))
print(f.write('world!'))
print(f.getvalue())  # getvalue()方法用于获得写入后的str

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

# 内存读取
f = StringIO('Hello Python!')
while True:
    s = f.readline()
    if s == '':
        break
    print(s.strip())

BytesIO

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO

from io import BytesIO

f = BytesIO()
f.write('中文'.encode('utf-8')) # 不是str,是经过UTF-8编码的bytes
print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
# 读取
f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
print(f.read())

(三)操作文件和目录

Python内置的os模块也可以直接调用操作系统提供的接口函数。如果是posix,说明系统是LinuxUnixMac OS X,如果是nt,就是Windows系统。os模块的某些函数是跟操作系统相关的

import os

print(os.name)  # 操作系统类型
print(os.uname())  # 详细系统信息 windows不提供

环境变量

在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:

print(os.environ)
environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'LENOVO-PC', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\Administrator', 'IDEA_INITIAL_DIRECTORY': 'D:\\PyCharm 2019.2.3\\bin', 'JAVA_HOME': 'D:\\Program Files\\Java\\jdk-9.0.4', 'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local', 'LOGONSERVER': '\\\\LENOVO-PC', 'NODE_PATH': 'D:\\Program Files\\nodejs\\node_global\\node_modules', 'NUMBER_OF_PROCESSORS': '6', 'OS': 'Windows_NT', 'PATH': ...)

获取某个环境变量的值,可以调用os.environ.get('key'):

print(os.environ.get('PATH'))
D:\code\PycharmProjects\PythonStudy\venv\Scripts;D:\app\Administrator\product\11.2.0\dbhome_1\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;D:\Python37;C:\Program Files\MySQL\MySQL Server 5.7\bin;D:\Program Files\Java\jdk-9.0.4\bin;D:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\;D:\Program Files\Microsoft SQL Server\100\Tools\Binn\;D:\Program Files\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files\Bandizip\;D:\Program Files\nodejs\node_global;C:\Users\Administrator\AppData\Roaming\npm\node_modules\yarn\bin;D:\Program Files (x86)\Yarn\bin

操作文件和目录

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中。

# 创建和删除目录
# 查看当前目录的绝对路径
print(os.path.abspath('.'))
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
print(os.path.join('D:\code\PycharmProjects\PythonStudy\venv\Include\IO编程', 'testDir'))
# 创建一个目录:
os.mkdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/testDir')
# 删除一个目录
os.rmdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/testDir')

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:part-1/part-2Windows下会返回这样的字符串:part-1\part-2。拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名。os.path.splitext()可以直接让你得到文件扩展名。合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。

# 拆分(后一部分总是最后级别的目录或文件名)
print(os.path.split('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt'))
# 获取文件扩展名
print(os.path.splitext('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt'))

shutil模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。
Python的特性来过滤文件:

# Python的特性来过滤文件 列出所有.py文件
l = [x for x in os.listdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程') if
     os.path.isfile(x) and os.path.splitext(x)[1] == '.py']
print(l)

(四)序列化

程序运行的过程中,所有的变量都是在内存中。定义一个dict:

d = dict(name = 'Tom',age = 19 ,score = 90)

可以随时修改变量,但是一旦程序结束,变量所占用的内存就被操作系统全部回收。如果没有把修改后的变量存储到磁盘上,下次重新运行程序,变量又被初始化修改前的变量。把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling

Python提供了pickle模块来实现序列化

import pickle

d = dict(name='tom', age=18, score=90)
# 序列化
p = pickle.dumps(d)
print(p)

b'\x80\x04\x95$\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x03tom\x94\x8c\x03age\x94K\x12\x8c\x05score\x94KZu.'

pickle.dumps()方法把任意对象序列化成一个bytes,然后就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object

# dump
f = open('D:/PycharmProjects/untitled/venv/Include/IO编程/dump.txt', 'wb')
pickle.dump(d,f)
f.close()

当要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象:

# 读取
f = open('D:/PycharmProjects/untitled/venv/Include/IO编程/dump.txt', 'rb')
d = pickle.load(f)
f.close()
print(d)

JSON

要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式。JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON表示的对象就是标准的JavaScript语言的对象,JSONPython内置的数据类型对应如下:

JSON类型 Python类型
{} dict
[] list
“String” str
1234.56 int\float
true/false True\False
null None
import json

d = dict(name='tom', age=20, score=90)
s = json.dumps(d)
print(s)

dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:

json_str = '{"age":20,"score":99,"name":"Tom"}'
str = json.loads(json_str)
print(str)

JSON标准规定JSON编码是UTF-8,所以我们总是能正确地在Python的strJSON的字符串之间转。

JSON进价

Python的dict对象可以直接序列化为JSON{},不过,很多时候,我们更喜欢用class表示对象,比如定义Student类,然后序列化。默认情况下,dumps()方法不知道如何将Student实例变为一个JSON{}对象。可选参数default就是把任意一个对象变成一个可序列为JSON的对象,我们只需要为Student专门写一个转换函数,再把函数传进去即可。

import json

class Student():
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score


def studentDict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }


s = Student('Tom', 14, 89)
print(json.dumps(s, default=studentDict))

# 任意class的实例变为dict
print(json.dumps(s, default=lambda obj: obj.__dict__))

通常class实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐