Python—IO编程(6)

76 阅读6分钟

文件的读写

磁盘上读写文件的功能都是由操作系统提供的,操作系统不允许普通的程序直接操作磁盘。

请求操作系统打开一个文件对象(通常称为文件描述符),通过操作系统提供的接口从这个文件对象中读取数据(读文件),把数据写入这个文件对象(写文件)。

  • 打开文件open(),获取一个文件对象。
  • 读取文件内容:通过 read()readline()readlines()等来获取文件中的内容。
  • 处理文件内容:对读取到的内容进行进一步的处理(例如解析、分析、修改等)。
  • 关闭文件: close() 方法关闭文件,以释放系统资源。

open() 打开文件

如果文件不存在,open()函数就会抛出一个IOError的错误

打开文件的模式:

  • 'r':只读模式(默认),文件必须存在。
  • 'w':写模式,文件不存在时会创建,文件存在时会覆盖内容。
  • 'a':追加模式,文件不存在时会创建,文件存在时会在末尾追加内容。
  • 'b':二进制模式,适用于处理非文本文件(如图片、音频等)。
  • 'r+':读写模式,文件必须存在。
# 文件路径:文件的路径,可以是绝对路径或相对路径。
# 模式:文件打开的模式(如读模式、写模式等)。
file = open('test.txt', 'r')
# content = file.read()  # 读取整个文件
# content = file.read(10)  # 读取10个字节
content = file.readline() # 读取一行数据
print(content)
file.close()

读取所有的行

file = open("test.txt", "r")
# 返回文件的所有行,并将读取到的行放到一个对象中
lines = file.readlines()
for line in lines:
    print(line)
file.close()

with 自动管理文件的打开和关闭

文件在退出 with 语句块时自动关闭,可以避免忘记关闭文件,它会在代码块执行完毕后自动关闭文件,哪怕发生异常。当然也可以使用 try ... finally

with open("test.txt", "r") as file:
    content = file.read()
    print(content)

逐行读取大文件

大文件读取调用 read() 会读取文件全部内容到内存中,可能导致内存不足。此时,可以反复调用read(size)方法,可以使用 for 循环逐行读取文件,避免一次性读取全部内容。

  • 小文件,read()一次性读取;
  • 不确定文件大小,反复调用read(size)比较保险;
  • 配置文件,调用readlines()最方便
with open("test.txt", "r") as file:
    for line in file:
        # strip() 去除每行末尾的换行符
        print(line.strip())

读取二进制文件

python 默认读取到的文本文件都是UTF-8,要读取二进制文件,比如图片、视频等,用'rb'模式打开文件

f = open('test.png', 'rb')
print(f.read())

读取文件指定字符编码

open()函数传入参数

# `encoding` 指定读取文件使用的字符编码
f = open('test_gbk.txt', 'r', encoding='gbk')
print(f.read())

当文本文件中夹杂了一些非法编码的字符,可能会遇到UnicodeDecodeErroropen()可接收一个errors参数,表示发生编码错误后的处理方式。最简单的处理方式是直接忽略

f = open('test_gbk.txt', 'r', encoding='gbk', errors='ignore')

写文件

调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:

写文件时,操作系统会先将数据是放到内存缓存起来,空闲的时候再慢慢写入。只有调用 close() 时,未写入的数据才会被保证写入磁盘。如果忘记调用 close(),可能导致数据丢失。

with open('test.txt', 'w') as f:
    f.write('王林 李慕婉!')

以追加的方式写入

with open('test.txt', 'a') as f:
    f.write('王林 李慕婉!')

StringIO 和 BytesIO

StringIOBytesIO 可在内存中操作 strbytes

StringIO

StringIO

from io import StringIO

f = StringIO()
f.write('王林')
f.write(" ")
f.write("李慕婉")
print(f.getvalue())

StringIO

# 可以初始化 `StringIO`
f = StringIO("hello\n王林\t李慕婉")
print(f.read())

BytesIO

要操作二进制数据,就需要使用BytesIO

from io import BytesIO

bytes_io = BytesIO()

bytes_io.write("修道,修仙,修真。神通,道法,仙法".encode('utf-8'))
print(bytes_io.getvalue())

os 模块操作文件和目录

操作系统提供了 dircdcpls 等命令来操作文件和目录,而在Python中内置的os模块就可以直接调用操作系统提供的接口函数。

import os
import shutil

# posix:Linux、Unix或Mac OS X
# nt:Windows系统
print(os.name)

# 要获取详细的系统信息
print(os.uname())  
# posix.uname_result(sysname='Darwin', nodename='cqqMacBook-Pro.local', release='23.4.0', version='Darwin Kernel Version 23.4.0: Fri Mar 15 00:11:05 PDT 2024; root:xnu-10063.101.17~1/RELEASE_X86_64', machine='x86_64')

# 获取系统的环境变量(操作系统中定义的环境变量,都保存在os.environ)
print(os.environ)

# 获取当前目录的绝对路径
print(os.path.abspath('.'))

# 在当前目录下,创建一个新目录
a = os.path.join('.', 'test')
os.mkdir(a)

# 删除一个目录
os.rmdir(a)

# os.path.join() 拼接的路径可正确处理不同操作系统的路径分隔符
# os.path.split() 拆分路径可正确处理不同操作系统的分割符
# os.path.split:后部分是文件或者最后一级目录
print(os.path.split('/path/to/test.txt')) # ('/path/to', 'test.txt')

# os.path.splitext:后部分是文件的扩展名
print(os.path.splitext('/path/to/test.txt')) # ('/path/to/test', '.txt')

# 文件重命名
os.rename('test.txt', 'new.txt')

# 删除文件
os.remove('new.txt')

# 复制文件
# shutil模块提供了copyfile(),shutil模块中有很多实用函数,可以看做是os模块的补充
shutil.copyfile('test.txt', 'new.txt')

# 过滤目录中的文件
files = [x for x in os.listdir(".") if os.path.isfile(x)]
print(files)

# 过滤说有的 .py 文件
py_files = [x for x in os.listdir(".") if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
print(py_files)

序列化、反序列化

Python的序列化过程称为 pickling 序列化后的内容可写入磁盘,或者在网络上传输。把变量内容从序列化的对象重新读到内存里称之为反序列化 unpickling

Python 的 pickle模块可实现序列化

import pickle

list0 = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
# pickle.dumps()将对象序列化成一个bytes
print(pickle.dumps(list0))

# 将对象序列化后写入一个file-like Object
with open('dump.txt', 'wb') as f:
    pickle.dump(list0, f)

# pickle.loads()方法反序列化出对象
# pickle.load()方法从一个file-like Object中直接反序列化出对象
with open('dump.txt', 'rb') as f:
    list1 = pickle.load(f)
    print(list1)

JSON 的序列化与反序列化

Python内置的json模块提供了对象和JSON的转换

import json

dict0 = dict(id=123, name='司徒南', email='1@1.com')
# 反序列化为 json
# ensure_ascii 保留中文字符
user_json = json.dumps(dict0, ensure_ascii=False)
print(user_json)  # {"id": 123, "name": "司徒南", "email": "1@1.com"}

# 序列化为对象
user = json.loads(user_json)
print(user)  # {'id': 123, 'name': '司徒南', 'email': '1@1.com'}

序列化对象,序列化对象需要自定义序列化和反序列化的函数

class User:
    def __init__(self, id_no, name, email):
        self.id_no = id_no
        self.name = name
        self.email = email


# 自定义序列化函数
def user_serializer(user):
    return {
        'id': user.id_no,
        'name': user.name,
        'email': user.email
    }


user = User(12, '李慕婉', '2@1.com')
# 序列化为json
# ensure_ascii=False 可以保留中文字符
user_obj_json = json.dumps(user, ensure_ascii=False, default=user_serializer)
print(user_obj_json)


# 自定义反序列化函数
def user_decoder(dct):
    if 'id' in dct and 'name' in dct and 'email' in dct:
        return User(dct['id'], dct['name'], dct['email'])
    return dct


# 将json load为对象
user_obj = json.loads(user_obj_json, object_hook=user_decoder)
print("Deserialized:", user_obj.id_no, user_obj.name, user_obj.email)