W
e
l
c
o
m
e
: )

10.文件操作(IO 技术)

本篇导航

本篇是 Python 文件与 IO 的总纲:§1~§2 文本/二进制文件的区别与相关模块总览 → §3 open() 八个参数详解(mode / buffering / encoding / errors / newline / closefd)→ §4 文本文件写入与中文编码(含 ASCII / GBK / Unicode / UTF-8 关系)→ §5 文本文件读取(read / readline / readlines / 迭代)→ §6 二进制文件读写与图片拷贝 → §7~§8 文件对象属性方法与任意位置定位(tell / seek)→ §9 StringIO / BytesIO / 类文件对象 → §10 pickle 序列化 → §11 CSV 读写 → §12 os 与 os.path 模块(含 walk() 递归遍历)→ §13 shutil 拷贝与压缩 → §14 递归算法。

阅读建议:§3 的 mode 与 encoding 是日常最容易出错的两个参数,务必吃透;§5 的四种读法与 §12 的 os.path 是实战高频;§11 CSV 的 utf-8-sig 是踩坑重灾区(见 §11.1)。

Pasted-image-20250405010607

一个完整的程序一般都包括数据的存储和读取;我们在前面写的程序数据都没有进行实际的存储,因此 Python 解释器执行完数据就消失了。实际开发中,我们经常需要从外部存储介质(硬盘、光盘、U 盘等)读取数据,或者将程序产生的数据存储到文件中,实现「持久化」保存。

有基础的同学知道,很多软件系统会把数据存储在数据库中;而数据库实际也是基于文件形式存储的。本章我们就来学习文件的相关操作。

1 文本文件和二进制文件

按文件中数据组织形式,我们把文件分为文本文件和二进制文件两大类。

  1. 文本文件

    文本文件存储的是普通「字符」文本,Python 默认为 Unicode 字符集(两个字节表示一个字符,最多可以表示:65536 个),可以使用记事本程序打开。但是,像 Word 软件编辑的文档不是文本文件。

  2. 二进制文件

    二进制文件把数据内容用「字节」进行存储,无法用记事本打开。必须使用专用的软件解码。常见的有:MP4 视频文件、MP3 音频文件、JPG 图片、doc 文档等等。

Pasted-image-20250405010823

原文勘误

原文称「Python 默认为 Unicode 字符集(两个字节表示一个字符,最多可以表示:65536 个)」,这是 UCS-2 时代的历史说法,作为「Unicode 字符集」的描述并不准确,已补充说明:

  • Python 3 的 str 是 Unicode 码点序列,与「几个字节」无关——一个元素就是「一个字符」(概念上的码点)。
  • 实测(Python 3.13.14):len("中文") == 2(是 2 个字符);写入磁盘时占多少字节取决于编码,"中文" 用 GBK 占 4 B、用 UTF-8 占 6 B。
  • 码点总数上限是 sys.maxunicode = 1114111(0x10FFFF,约 111 万),远超 65536;BMP 之外的字符(如 )也是单个字符。

正确的理解是:「字符集」决定能表示哪些字符,「编码」决定每个字符写进文件占几个字节。

2 文件操作相关模块概述

Python 标准库中与文件操作相关的模块如下,后文会陆续介绍。

名称 说明
io 模块 文件流的输入和输出操作 input output
os 模块 基本操作系统功能,包括文件操作
glob 模块 查找符合特定规则的文件路径名
fnmatch 模块 使用模式来匹配文件路径名
fileinput 模块 处理多个输入文件
filecmp 模块 用于文件的比较
csv 模块 用于 csv 文件处理
pickle 和 _pickle 用于序列化和反序列化
xml 包 用于 XML 数据处理
bz2、gzip、zipfile、zlib 、tarfile 用于处理压缩和解压缩文件(分别对应不同的算法)
原文勘误

上表原文有两处需要更正:

  1. 原文写作 cvs 模块,拼写错误。实测 import cvs 会 ModuleNotFoundError,正确的模块名是 csv(Comma Separated Values),已更正。
  2. 原文写作「pickle 和 cPickle」。Python 3 已不存在 cPickle 模块(实测 import cPickle 报 ImportError),其 C 实现已被合并进 pickle(内部即 _pickle),直接用 pickle 即可,已更正为 pickle 和 _pickle。

3 创建文件对象 open()

open() 函数用于创建文件对象,基本语法格式如下:

open(file, mode='r', buffering=-1, encoding=None, errors= None, newline=None, closefd=True, opener=None)

打开一个文件,返回一个文件对象(流对象)和文件描述符。打开文件失败,则返回异常

原文勘误

原文此处写「打开文件失败,则返回异常」——这是概念性错误。失败时 open() 不是「返回」异常对象,而是抛出(raise)异常,必须用 try…except 或 with 处理。例如打开不存在的文件会抛 FileNotFoundError。

基本使用:创建一个文件 test,打开它,用完关闭

f = open("test")  # file对象
# windows <_io.TextIOWrapper name='test' mode='r' encoding='cp936'>
# linux <_io.TextIOWrapper name='test' mode='r' encoding='UTF-8'>
print(f.read())  # 读取文件
f.close()
原文勘误

原文末行写作 f.c1ose(),把字母 l 误打成了数字 1。这会让程序抛 AttributeError: '_io.TextIOWrapper' object has no attribute 'c1ose',已更正为 f.close()。

文件操作中,最常用的操作就是读和写。

文件访问的模式有两种:文本模式和二进制模式。不同模式下,操作函数不尽相同,表现的结果也不一样。

注:

Windows 代码页

windows 中使用 codepage 代码页,可以认为每一个代码页就是一张编码表。cp936 等同于 GBK 。

如果只写文件名,代表当前目录下的文件;也可以写全路径,例如 D:\a\b.txt。

为了减少 \ 的输入,可以使用原始字符串: r"d:\b.txt"。示例如下:

f = open (r"d:\b.txt", "w")

3.1 open() 参数

file

打开或者要创建的文件名。如果不指定路径,默认是当前路径

mode 模式 ★★★

打开方式有如下几种:

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
x 只写,不支持读取。
文件存在,抛异常——文件存在异常。
文件不存在,创建新文件,从头写。
t t 是 text ,文本模式,默认可以不写。
t 模式不能单独使用,必须和 rwax 四种之一配合使用,不影响主模式
b b 是 binary , 二进制
b 模式不能单独使用,必须和 rwax 四种之一配合使用,不影响主模式
mode 记忆口诀

虽然 mode 文件操作模式很多,但是我们只需要记住 3 个字符即可。r、w、a

  • r+、w+、a+,带加号,功能全,既能读,又能写(区别在于指针到底指向不同)
  • rb、wb、ab,代 b 的字符,代表以二进制的形式对其进行操作,适合读取文本或二进制格式文件,如图片、音频、视频等格式
  • rb+、wb+、ab+,带加号,功能全,既能读,又能写(区别在于指针到底指向不同)

文本文件对象和二进制文件对象的创建:

  1. 如果我们没有增加模式 b,则默认创建的是文本文件对象,处理的基本单元是字符。
  2. 如果是二进制模式 b,则创建的是二进制文件对象,处理的基本单元是字节。

buffering 缓冲区

  1. -1 表示使用缺省大小的 buffer。
  2. 如果是二进制模式,使用 io.DEFAULT_BUFFER_SIZE 值,默认是 4096 或者 8192 。
  3. 如果是文本模式,且是终端设备,是行缓存方式;如果不是,则使用二进制模式的策略。
  • 0,只在二进制模式使用,表示关闭 buffer
  • 1,只在文本模式使用,表示使用行缓冲。意思就是见到换行符就 flush
  • 大于 1,用于指定 buffer 的大小

buffer 缓冲区

缓冲区是一个内存空间,一般来说是一个 FIFO 队列,当缓冲区满了或者达到阈值,数据才会 flush 到磁盘。

flush() 将缓冲区数据写入磁盘

close() 关闭前会调用 flush()

io.DEFAULT_BUFFER_SIZE 查看缺省缓冲区大小,单位是字节

  • buffering = -1

    使用默认缓冲区大小。一般缓冲区默认大小为 4096 (4K) 或 8192 (8K) 。

  • buffering = 0

    文本模式不支持。

    二进制模式无缓冲区,哪怕只有一个字节,立即写入磁盘,文件当做一个 FIFO 队列了。

  • buffering = 1

    文本模式,行缓冲模式,认 \n ,遇到 \n 就输出,连同当前 \n 前的字符一起写入磁盘;文本模式下,如果缓冲区撑破了,会被迫写入磁盘。

    二进制模式,使用默认缓冲区。

  • buffering > 1

    文本模式,使用默认缓冲区大小。

    二进制模式,指定大小字节的缓冲区。

先看二进制模式(jupyter notebook 中一行一行运行,查看效果)

import io

f = open('test', 'w+b')
# 缺省缓冲区大小
print(io.DEFAULT_BUFFER_SIZE)  # IPython 中为 8192
f.write("小龙女".encode())
# cat test
f.seek(0)
# cat test
f.write("杨过".encode())
f.flush()
f.close()

# 缓冲区大小为 0
f = open("test", "w+b", 0)
f.write(b'a')
# cat test
f.write(b'a')
# cat test
f.close()

# 缓冲区大小为 1
f = open("test", "w+b", 1)
f.write(b'a')
# cat test
f.write(b'a' * 200)
# cat test
f.write(b'a' * 8000)
# cat test
f.close()

# 设置缓冲区大小
f = open("test", "w+b", 4)
f.write(b'php')
# cat test
f.write(b'python')
# cat test
f.close()

二进制模式

  • -1 是缺省值,使用缺省缓冲区大小
  • 0 关闭缓冲区,不需要内存的 buffer ,可以看做是一个 FIFO 的文件
  • 1 直接使用缺省缓冲区大小
  • 大于 1,使用指定缓冲区大小

文本模式

# buffering=0,行吗?
# f = open('test4', 'w+', 0)

# buffering=1,使用行缓冲
f = open('test4', 'w+', 1)
f.write("php")
# cat test4
f.write("python" * 4)  #cat test4
f.write('\n')
# cat test4
f.write('Hello\nPython')
# cat test4
f.close()

# buffering>1,使用默认大小的缓冲区
f = open('test4', 'w+', 15)
f.write("php")
# cat test4
f.write('java')
# cat test4
f.write('Hello\n')
# cat test4
f.write('\nPython')
# cat test4
f.write('a' * (io.DEFAULT_BUFFER_SIZE - 20))  # 设置为大于1没什么用
f.write('\nwww.python.com/python')
f.close()

文本模式

  • -1 是缺省值,使用缺省缓冲区大小

  • 0 不支持

    image-20221029152515161

  • 1 行缓冲模式,认 \n ,遇到 \n 就输出,连同当前 \n 前的字符一起写入磁盘;文本模式下,如果缓冲区撑破了,会被迫写入磁盘。

  • 大于 1,默认缓冲区大小

buffering 说明
buffering = -1 t 和 b,都使用 io.DEFAULT_BUFFER_SIZE 大小
buffering = 0 b 关闭缓冲区
t 不支持
buffering = 1 b 不支持
t 行缓冲,遇到换行符才 flush
buffering > 1 b 模式表示缓冲大小。缓冲区的值可以超过 io.DEFAULT_BUFFER_SIZE ,直到设定的值超出后才把缓冲区 flush
t 模式,是 io.DEFAULT_BUFFER_SIZE 字节,flush 完后把当前字符串也写入磁盘

看起来有些麻烦,但一般来说只需记住:

  1. 文本模式,一般都用默认缓冲区大小
  2. 二进制模式,是一个个字节的操作,可以指定 buffer 的大小
  3. 一般来说,默认缓冲区大小是个比较好的选择,除非明确知道,否则不调整它
  4. 一般编程中,明确知道需要写磁盘了,都会手动调用一次 flush ,而不是等到自动 flush 或者 close 的时候
本机实测(Python 3.13.14,Windows)

上表的口径已逐条实测确认,另有两点补充:

场景 实测结果
io.DEFAULT_BUFFER_SIZE 8192
open(f, 'w+', 0) 文本模式 ValueError: can't have unbuffered text I/O
open(f, 'w+b', 1) 二进制模式 不报错,但发 RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode,实际回退为默认缓冲区
文本 buffering=1 写 "php" 不换行 磁盘仍为 0 字节;写完 "\n" 后变 5 字节 → 证实行缓冲认 \n
文本 buffering=15 写 "php" 磁盘为 0 字节 → 证实文本模式 大于 1 时仍用默认块缓冲(设多大都一样)
二进制 buffering=0 写 b"a" 磁盘立即变 1 字节 → 证实无缓冲,立即落盘

encoding

encoding 属性,指定编码,仅文本模式下使用。

None 表示使用缺省编码,依赖操作系统。

  • windows 下缺省 GBK(0xB0A1)
  • Linux 下缺省 UTF-8(0xE5 95 8A)
实测补充:默认编码怎么看、会不会被环境变量改掉

原文「Windows 下缺省 GBK」在默认情况下成立,但要注意 Python 3.15 起默认编码将统一为 UTF-8,且环境变量会改变它。本机实测:

① 干净环境(无 PYTHON* 环境变量)子进程:
   locale.getpreferredencoding(False) = 'cp936'   ← 即 GBK
   文本模式默认 encoding              = 'cp936'
② 本会话环境(带 PYTHONUTF8=1):
   sys.flags.utf8_mode                = 1
   locale.getpreferredencoding(False) = 'utf-8'
   文本模式默认 encoding              = 'utf-8'

所以「默认编码」并非恒定:若设置了 PYTHONUTF8=1(或 -X utf8),文本模式默认编码就是 UTF-8。写涉及中文的文件时,始终显式传 encoding= 才是最稳妥的写法。

errors

什么样的编码错误将被捕获

None 和 strict 表示有编码错误将抛出 ValueError 异常;

ignore 表示忽略

原文勘误

原文称编码错误「将抛出 ValueError 异常」,类型不对。实测(Python 3.13.14)用 encoding='utf-8' 读 GBK 文件,抛的是 UnicodeDecodeError(它是 ValueError 的子类,但报出的类型名是 UnicodeDecodeError)。

补充 errors 三种取值的实测差异(读 GBK 文件用 utf-8 解码):

errors 结果
None(等同 strict) 抛 UnicodeDecodeError
'ignore' 直接忽略非法字节 → ''
'replace' 用替换符代替 → '����'

newline

文本模式中,换行的转换。取值可以为 None、""、\r、\n、\r\n

写时

  • None,缺省值,表示 \n 都会被替换为系统缺省行分隔符 os.linesep;
  • '\n' 或 '' 表示 \n 不替换;
  • 其它合法换行字符表示文本中的 \n 换行符会被替换为指定的换行字符
f = open('test', 'w')
# newline 缺省为 None,windows 下会把 \n 替换为 \r\n
f.write('python\rwww.python.org\nwww.java.org\r\npython3')
# 真正写入的是,将 \n 替换为 windows 的默认换行符 \r\n
# 'python\rwww.python.org\r\nwww.java.org\r\r\npython3'
f.close()

image-20221029164312886

读时

  • None,缺省值,表示 \r、\n、\r\n 都被转换为 \n ;
  • '' 表示不会自动转换通用换行符,常见换行符都可以认为是换行;
  • 其它合法换行字符表示按照该字符分行。
newlines = ['', None, '\n', '\r\n']
for nl in newlines:
    f = open('test', newline=nl)  #缺省替换所有换行符
    print(f.readlines())
    f.close()

# 运行结果如下
'' 表示什么都不做,常见换行符都用来分行
['python\r', 'www.python.org\r\n', 'www.java.org\r', '\r\n', 'python3']

None 常见换行符都替换为 \n ,用 \n 来分行
['python\n', 'www.python.org\n', 'www.java.org\n', '\n', 'python3']

\n 做为换行符
['python\rwww.python.org\r\n', 'www.java.org\r\r\n', 'python3']

\r\n 做为换行符
['python\rwww.python.org\r\n', 'www.java.org\r\r\n', 'python3']

image-20221029164528135

本机实测:写入侧磁盘字节 + 读取侧四种取值

写入 'python\rwww.python.org\nwww.java.org\r\npython3' 后,磁盘实际字节(hexdump 形态):

b'python\rwww.python.org\r\nwww.java.org\r\r\npython3'

即仅把裸 \n 替换成 \r\n,原本的 \r\n 保持不变(所以 www.java.org\r\r\n 有两个 \r)——与原文注释完全一致。

读取侧四种 newline 取值的实测输出:

newline=''      -> ['python\r', 'www.python.org\r\n', 'www.java.org\r', '\r\n', 'python3']
newline=None    -> ['python\n', 'www.python.org\n', 'www.java.org\n', '\n', 'python3']
newline='\n'    -> ['python\rwww.python.org\r\n', 'www.java.org\r\r\n', 'python3']
newline='\r\n'  -> ['python\rwww.python.org\r\n', 'www.java.org\r\r\n', 'python3']

与原文一致:None 把三种换行统一成 \n;'\n' 与 '\r\n' 只按自身切分,故 \r 不构成换行(只剩 3 行)。

closefd

每个文件打开时都会得到一个文件描述符,关闭时会把文件描述符交还回去,表示释放资源。

关闭文件描述符,True 表示关闭它。False 会在文件关闭后保持这个描述符。fileobj.fileno() 查看

f = open('test')
# 查看文件描述符
print(f.fileno())

f.close()
# 查看文件是否被关闭
print(f.closed)
# 文件描述符会在文件关闭后交还
print(f.fileno())

image-20221029170403177

实测补充:fileno() / closed 的行为
f = open('t5.txt', 'w')
f.fileno()  ->  3          (具体数值随进程已打开文件数变化,不是固定的)
f.closed    ->  False
f.close()
f.closed    ->  True
f.fileno()  ->  ValueError: I/O operation on closed file

即关闭后 closed 变 True,再调 fileno() 会抛 ValueError(原文最后一行 print(f.fileno()) 正是演示这个报错)。

4 文本文件的写入

4.1 基本的文件写入操作

文本文件的写入一般就是三个步骤:

  1. 创建文件对象
  2. 写入数据
  3. 关闭文件对象

案例:文本写入操作简单测试

f = open(r"a.txt", "a")
s = "itbaizhan\nsxt\n"
f.write(s)
f.close()

或者使用 with 上下文管理:

with open(r"a.txt", "a") as f:
    s = "itbaizhan\nsxt\n"
    f.write(s)

image-20220703085845837

4.2 常用编码介绍

操作文本文件时经常要处理中文,这时就容易碰到乱码问题。为了能解决中文乱码,这里简单介绍一下各种编码之间的关系。

常用编码之间的关系如下:

image-20220703090047825

ASCII

全称为 American Standard Code for Information Interchange ,美国信息交换标准代码,这是世界上最早最通用的单字节编码系统,主要用来显示现代英语及其他西欧语言。

ASCII 码用 7 位表示 ,只能表示 128 个字符 。只定义了 $2^7=128$ 个字符 ,用 7bit 即可完全编码,而一字节 8bit 的容量是 256 ,所以一字节 ASCII 的编码最高位总是 0。

0~31 表示控制字符如回车、退格、删除等;32~126 表示打印字符即可以通过键盘输入并且能显示出来的字符;其中 48~57 为 0 到 9 十个阿拉伯数字,65~90 为 26 个大写英文字母,97~122 号为 26 个小写英文字母,其余为一些标点符号、运算符号等,具体可以参考 ASCII 标准表(大家自行百度,不在此赘述)。

ISO8859-1

ISO-8859-1 又称 Latin-1 ,是一个 8 位单字节字符集,它把 ASCII 的最高位也利用起来,并兼容了 ASCII ,新增的空间是 128 ,但它并没有完全用完。

在 ASCII 编码之上又增加了西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,它是向下兼容 ASCII 编码

GB2312,GBK,GB18030

GB2312

GB2312 全称为信息交换用汉字编码字符集,是中国于 1980 年发布,主要用于计算机系统中的汉字处理。GB2312 主要收录了 6763 个汉字、682 个符号。

GB2312 覆盖了汉字的大部分使用率,但不能处理像古汉语等特殊的罕用字,所以后来出现了像 GBK 、GB18030 这种编码。

GB2312 完全兼容 ISO8859-1。

GBK

全称为 Chinese Internal Code Specification ,即汉字内码扩展规范,于 1995 年制定。它主要是扩展了 GB2312 ,在它的基础上又加了更多的汉字,它一共收录了 21003 个汉字

GB18030

现在最新的内码字集于 2000 年发布,并于 2001 年强制执行,包含了中国大部分少数民族的语言字符,收录汉字数超过 70000 余个。

它主要采用单字节、双字节、四字节对字符编码,它是向下兼容 GB2312 和 GBK 的,虽然是我国的强制使用标准,但在实际生产中很少用到,用得最多的反而是 GBK 和 GB2312

Unicode

Unicode 编码设计成了固定两个字节,所有的字符都用 16 位 ($2^{16}=65536$) 表示,包括之前只占 8 位的英文字符等,所以会造成空间的浪费,UNICODE 在很长的一段时间内都没有得到推广应用。

Unicode 完全重新设计,不兼容 iso8859-1,也不兼容任何其他编码。

UTF-8

对于英文字母,unicode 也需要两个字节来表示。所以 unicode 不便于传输和存储。因此而产生了 UTF 编码,UTF-8 全称是(8-bit Unicode Transformation Format)。

UTF 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符,不过,UTF 编码是不定长编码,每一个字符的长度从 1~4 个字节不等。其中,英文字母都是用一个字节表示,而汉字使用三个字节。

建议

一般项目都会使用 UTF-8 。unicode 中虽然汉字是两个字节,UTF-8 中汉字是 3 个字节。但是互联网中一个网页也包含了大量的英文字母,这些英文字母只占用 1 个字节,整体占用空间,UTF-8 仍然优于 Unicode 。

4.3 中文乱码问题

windows 操作系统默认的编码是 GBK ,Linux 操作系统默认的编码是 UTF-8 。当我们用 open() 时,调用的是当前操作系统打开的文件,默认的编码是 GBK (Windows)。

案例:中文字符文件,乱码出现测试

# 测试写入中文
f = open(r"b.txt", "w")
f.write("尚学堂 \n 百战程序员 \n ")
f.close()

运行结果( Linux 环境中不存在这个问题):

image-20220703093155624

在文件编辑区单击右键,选择 FileEncoding,再选 GBK 即可:

image-20220703093417656

再选择 Reload,文件即显示正常。

image-20220703093445204

案例:通过指定文件编码解决中文乱码问题

# 测试写入中文
f = open(r"b.txt", "w", encoding="utf-8")
f.write("尚学堂 \n 百战程序员 \n ")
f.close()

image-20220703093651107

4.4 write()、writelines() 写入数据

write(a): 把字符串 a 写入到文件中

writelines(b):把字符串列表写入文件中,不添加换行符

案例:添加字符串列表数据到文件中

f = open(r"./bb.txt", "w", encoding="utf-8")
s = ["高淇\n", "高老三\n", "高老四\n"]
f.writelines(s)
f.close()

image-20220703094045112

原文勘误

原文此处写作 encoding="utf-8 ",utf-8 后面多了一个空格。本机实测该写法不会报错(codecs.lookup 会 strip 空白,f.encoding 显示为 'utf-8 '),但属于不严谨的写法,容易在其他实现/工具链上出问题,已去掉多余空格。

4.5 close() 关闭文件流

Pasted-image-20250405023431

由于文件底层由操作系统控制,所以打开的文件对象必须显式调用 close() 方法关闭。close() 会先把缓冲区数据写入文件(也可以直接调用 flush() 方法),再关闭文件,释放文件对象。

为了确保打开的文件对象正常关闭,一般结合异常机制的 finally 或者 with 关键字实现无论何种情况都能关闭打开的文件对象。

案例:结合异常机制 finally 确保关闭文件对象

try:
    f = open(r"my01.txt", "a")
    str = "gaoqi"
    f.write(str)
except BaseException as e:
    print(e)
finally:
    f.close()
原文勘误(真实缺陷)

上面这段代码有隐患:f = open(...) 写在 try 内部,一旦 open() 本身就失败(如目录不存在、无权限),f 根本没被赋值,finally 里的 f.close() 会再抛一个 UnboundLocalError,把原始异常「盖掉」。

实测(Python 3.13.14,打开不存在的目录):

except 捕获: FileNotFoundError          ← try 里先接住了 open 的异常
❗ 最终抛出 UnboundLocalError: cannot access local variable 'f' where it is not associated with a value

正确写法:把 open() 放在 try 之外,或先给 f 赋初值,或用 with(推荐,见 §4.6):

# 写法一:open 放在 try 外(open 失败直接抛出,不进 finally)
f = open(r"my01.txt", "a")
try:
    f.write("gaoqi")
finally:
    f.close()

# 写法二:先赋值 None,finally 里判空
f = None
try:
    f = open(r"my01.txt", "a")
    f.write("gaoqi")
except BaseException as e:
    print(e)
finally:
    if f is not None:
        f.close()

4.6 with 语句 (上下文管理器 )

with 关键字(上下文管理器)可以自动管理上下文资源,不论什么原因跳出 with 块,都能确保文件正确的关闭,并且可以在代码块执行完毕后自动还原进入该代码块时的现场。

案例:使用 with 管理文件写入操作

s = ["高淇\n", "高老三\n", "高老五\n"]
with open(r"./bb.txt", "w") as f:
    f.writelines(s)
    print(f, f.closed)

# 测试 f 是否关闭
f.closed

上下文管理

  1. 使用 with 关键字,上下文管理针对的是 with 后的对象
  2. 可以使用 as 关键字
  3. 上下文管理的语句块并不会开启新的作用域

文件对象上下文管理

  1. 进入 with 时,with 后的文件对象是被管理对象
  2. as 子句后的标识符,指向 with 后的文件对象
  3. with 语句块执行完的时候,会自动关闭文件对象

另一种写法

f1 = open('test')
print(f1.closed)
with f1:
    # 文件只读,写入失败
    # f1.write("abc")
    print(f1, f1.closed)

# 测试 f 是否关闭
print(f1.closed)

image-20221029175733145

  • 对于类似于文件对象的 IO 对象,一般来说都需要在不使用的时候关闭、注销,以释放资源。
  • IO 被打开的时候,会获得一个文件描述符。计算机资源是有限的,所以操作系统都会做限制。就是为了保护计算机的资源不要被完全耗尽,计算资源是很多程序共享的,不是独占的。
  • 一般情况下,除非特别明确的知道当前资源情况,否则不要盲目提高资源的限制值来解决问题。

5 文本文件的读取

文件的读取一般使用如下三个方法:

  1. read([size])
    从文件中读取 size 个字符,并作为结果返回。如果没有 size 参数,则读取整个文件。读取到文件末尾,会返回空字符串。

  2. readline([limit])
    读取一行内容作为结果返回。读取到文件末尾,会返回空字符串。limit 表示从本行中读取几个字符。

  3. readlines()
    文本文件中,每一行作为一个字符串存入列表中,返回该列表。

本机实测(Python 3.13.14,Windows)

以内容为 java␊Python␊中文行␊last 的 bb.txt(UTF-8)为例:

read(4)          -> 'java'                              (按【字符】计数)
read(5)          -> 'java\n'
readline()       -> 'java\n'
readline(3)      -> 'jav'                               (limit 也是【字符】数)
readlines()      -> ['java\n', 'Python\n', '中文行\n', 'last']
读完后 read()    -> ''                                  (EOF 返回空串)
读完后 readlines() -> []

三个方法都以字符为基本单位(这正是文本模式与二进制模式的分界线:二进制模式按字节);读到 EOF 一律返回空串 / 空列表。

案例:读取一个文件前 4 个字符

with open(r"bb.txt", "r", encoding="utf-8") as f:
    print(f.read(4))

案例:文件较小,一次将文件内容读入到程序中

with open(r"./bb.txt", "r") as f:
    print(f.read())
原文勘误(易踩坑)

上面这段代码没有显式指定 encoding,读取时用哪个编码完全由「系统默认编码」决定:

Windows 中文环境(本机 cp936 / GBK):读 UTF-8 中文文件 → UnicodeDecodeError
Linux / macOS(默认 UTF-8):         正常读取

所以读写含中文的文本务必显式写 encoding="utf-8"(读带 BOM 的文件用 utf-8-sig,读 GBK 文件用 gbk),否则换一台机器就可能报错。

案例:按行读取一个文件

with open(r"bb.txt", "r", encoding='utf-8') as f:
    while True:
        fragment = f.readline()

        if not fragment:
            break
        else:
            print(fragment, end="")

Snipaste_2022-07-03_09-56-35

案例:使用迭代器(每次返回一行)读取文本文件

with open(r"./bb.txt", "r", encoding='utf-8') as f:
    for a in f:
        print(a, end="")

Snipaste_2022-07-03_09-59-38

案例:为文本文件每一行的末尾增加行号

with open("bb.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()
    # 推导式生成列表
    lines = [line.rstrip() + " #" + str(index + 1) + "\n" for index, line in enumerate(lines)]

with open("bb.txt", "w", encoding="utf-8") as f:
    f.writelines(lines)

执行前:

image-20220703100506814

执行后:

image-20220703100611421

6 二进制文件的读取和写入

二进制文件的处理流程和文本文件流程一致。首先还是要创建文件对象,不过,我们需要指定二进制模式,从而创建出二进制文件对象。例如:

f = open(r"d:\a.txt", 'wb')  # 可写的、重写模式的二进制文件对象
f = open(r"d:\a.txt", 'ab')  # 可写的、追加模式的二进制文件对象
f = open(r"d:\a.txt", 'rb')  # 可读的二进制文件对象

创建好二进制文件对象后,仍然可以使用 write() 、read() 实现文件的读写操作。

案例:读取图片文件,实现文件的拷贝(注意指针位置)

Pasted-image-20250406144724

with open(r'ly.jpeg', 'rb') as f:
    with open('ly_back.jpeg', 'wb') as w:
        print(f.readline())
        print(f.tell())
        f.seek(0, 0)  # 非常重要!!!
        print(f.readlines())
        print(f.tell())
        # 重置指针到开头
        f.seek(0, 0)  # 非常重要!!!
        for line in f.readlines():
            w.write(line)
print('图片拷贝完成!')

Pasted-image-20250408093143
Pasted-image-20250408093104

警告:以下写法是错误的!!!

以下写法是错误的!!!文件对象是一个可迭代对象,指针在每一次读取后都会向后移动。所以在再次读取之前应该将指针复位到 0 。

with open(r'ly.jpeg', 'rb') as f:
    with open('ly_back.jpeg', 'wb') as w:
        print(f.readline())
        print(f.readlines())
        for line in f.readlines():
            w.write(line)
print('图片拷贝完成!')

实际执行结果:ly_back.jpeg 大小为 0 字节——readline() 与 readlines() 已把指针推到 EOF,循环里的 readlines() 只能返回空列表,一个字节也写不进去。

为什么二进制拷贝里要用 readlines() 逐块写

二进制文件里的 0x0a(即 \n)只是碰巧出现的普通数据字节,并不是"行"。所以 readline() 在二进制流里按第一个 0x0a 截断——切出来的"一行"没有任何文本语义,这里只是借它演示"指针会前移"。

工程上拷贝二进制文件一般直接分块读,不做无谓的 readline():

with open('ly.jpeg', 'rb') as f, open('ly_back.jpeg', 'wb') as w:
    while chunk := f.read(8192):   # 每次 8 KB
        w.write(chunk)

7 文件对象的常用属性和方法

文件对象封装了文件相关的操作。前面已学习了通过文件对象对文件进行读写,本节详细列出文件对象的常用属性和方法,并加以说明。

文件对象的属性:

属性 说明
name 返回文件的名字
mode 返回文件的打开模式
closed 若文件被关闭则返回 True

文件对象的打开模式:

模式 说明
r 读模式
w 写模式
a 追加模式
b 二进制模式(可与其他模式组合)
+ 读写模式(可以其他模式组合)

文件对象的常用方法:

方法名 说明
read([size]) 从文件中读取 size 个字节或字符的内容返回。若省略 [size],则读取到文件末尾,即一次读取文件所有内容
readline() 从文本文件中读取一行内容
readlines() 把文本文件中每一行都作为独立的字符串对象,并将这些对象放入列表返回
write(str) 将字符串 str 内容写入文件
writelines(s) 将字符串列表 s 写入文件文件,不添加换行符
seek(offset[,whence]) 把文件指针移动到新的位置,offset 表示相对于 whence 的多少个字节的偏移量;
offset:off 为正往结束方向移动,为负往开始方向移动
whence:不同的值代表不同含义
&nbsp&nbsp&nbsp&nbsp0:从文件头开始计算(默认值)
&nbsp&nbsp&nbsp&nbsp1:从当前位置开始计算
&nbsp&nbsp&nbsp&nbsp2:从文件结尾开始计算
tell() 返回文件指针的当前位置
truncate([size]) 不论指针在什么位置,只留下指针前 size 个字节的内容,其余全部删除;
如果没有传入 size ,则将指针当前位置到文件末尾内容全部删除
flush() 把缓冲区的内容写入文件,但不关闭文件
close() 把缓冲区内容写入文件,同时关闭文件,释放文件对象相关资源

8 文件任意位置操作

  • 文件指针 指向当前字节位置

    mode=r 指针起始在 0

    mode=a 指针起始在 EOF

  • tell() 显示指针当前位置

  • seek(offset[, whence])

    移动文件指针位置。offset 偏移多少字节,whence 从哪开始

  1. seek() 方法操作文本文件
  • whence 0 缺省值,表示从头开始,offset 只能正整数,可以右超界,不能左超界

    image-20221023230327248

  • whence 1 表示从当前位置,offset 只接受 0

    whence 2 表示从 EOF 开始,offset 只接受 0

    image-20221023230450162

  • 文本模式支持从开头向后偏移的方式。

  • whence 为 1 表示从当前位置开始偏移,但是只支持偏移 0,相当于原地不动,所以没什么用。

  • whence 为 2 表示从 EOF 开始,只支持偏移 0,相当于移动文件指针到 EOF。

  • seek 是按照字节偏移的。

  • read 在文本模式是按照字符读取的。

  1. seek() 方法操作二进制文件
  • whence 0 缺省值,表示从头开始,offset 只能正整数
  • whence 1 表示从当前位置,offset 可正可负
  • whence 2 表示从 EOF 开始,offset 可正可负
  • 二进制模式支持任意起点的偏移,从头、从尾、从中间位置开始
  • 向后 seek 可以超界,但是向前 seek 的时候,不能超界,否则抛异常。

案例:seek() 移动文件指针示例

with open("bb.txt", "r", encoding="utf-8") as f:
    print("文件名是:{0}".format(f.name))
    print(f.tell())
    print("读取的内容:{0}".format(str(f.readline())))
    print(f.tell())
    f.seek(0, 0)
    print("读取的内容:{0}".format(str(f.readline())))

Snipaste_2022-07-03_10-55-25

本机实测:文本模式 seek 的边界到底在哪

文件内容 abc中文def(UTF-8 共 12 字节):

文本 seek(3, 0)   -> OK     read() = '中文def'      (正好落在字符边界)
文本 seek(4, 0)   -> UnicodeDecodeError             (落在"中"的字节中间)
文本 seek(100, 0) -> OK     read() = ''             (右超界允许)
文本 seek(-1, 0)  -> ValueError: negative seek position -1  (左超界直接报错)
文本 seek(0, 1)   -> OK     (whence=1 仅接受 offset=0)
文本 seek(1, 1)   -> UnsupportedOperation: can't do nonzero cur-relative seeks
文本 seek(0, 2)   -> OK     (跳到 EOF)
文本 seek(-1, 2)  -> UnsupportedOperation: can't do nonzero end-relative seeks
二进制 seek(100, 0) -> OK   (二进制任意超界都不报错,读到就是空)
二进制 seek(-1, 0)  -> OSError: [Errno 22] Invalid argument

原文"offset 只能正整数,可以右超界,不能左超界"完全成立;补充两点:
① 文本模式 whence=1 / 2 只接受 offset = 0,其余一律 UnsupportedOperation;
② 文本模式按字节偏移,若落在多字节字符中间,读取时抛 UnicodeDecodeError(二进制模式无此限制)。

本机实测:文本模式的 tell() 返回的是「不透明 cookie」

文本模式下 tell() 不等于字节数。默认开启换行翻译时,缓冲区里可能有"已读入但尚未返回"的字节,此时 tell() 会返回一个巨大的不透明值:

文件磁盘字节 b'java\r\nPython\r\n...'
read(4) -> 'java'     tell() = 18446744073709551621   # 即 2**64 + 5
seek(该值) -> 正确定位(read() = '\nPython\n中文行\nlast')

要点:把 tell() 的返回值原样回传给 seek() 是安全的(它是"锚点"),但不要拿它做算术、也不要当成"当前读到第几个字节"。需要真实字节偏移时用二进制模式(tell() 恒等于字节数)。

9 StringIO 和 BytesIO

9.1 StringIO

  • io 模块中的类
    • from io import StringIO
  • 内存中,开辟的一个文本模式的 buffer ,可以像文件对象一样操作它
  • 当 close 方法被调用的时候,这个 buffer 会被释放

getvalue() 获取全部内容。跟文件指针没有关系

from io import StringIO

#内存中构建
sio = StringIO()  # 像文件对象一样操作
print(sio.readable(), sio.writable(), sio.seekable())
sio.write("java\nPython")
sio.seek(0)
print(sio.readline())
print(sio.getvalue())  # 无视指针,输出全部内容
sio.close()

image-20221029180519709

好处

一般来说,磁盘的操作比内存的操作要慢得多,内存足够的情况下,一般的优化思路是少落地(即写入磁盘),减少磁盘 IO 的过程,可以大大提高程序的运行效率。

9.2 BytesIO

  • io 模块中的类
    • from io import BytesIO
  • 内存中,开辟的一个二进制模式的 buffer ,可以像文件对象一样操作它
  • 当 close 方法被调用的时候,这个 buffer 会被释放
from io import BytesIO  # 内存中构建

bio = BytesIO()
print(bio.readable(), bio.writable(), bio.seekable())
bio.write(b"java\nPython")
bio.seek(0)
print(bio.readline())
print(bio.getvalue())  # 无视指针,输出全部内容
bio.close()

image-20221029181006877

本机实测:StringIO / BytesIO 的真实输出
StringIO: True True True  /  'java\n'  /  'java\nPython'
BytesIO:  True True True  /  b'java\n'  /  b'java\nPython'

两者都是可读、可写、可 seek 的;getvalue() 无视当前指针返回全部内容——这正是它与普通文件对象的关键差别(普通文件读到哪就是哪,没有"取全量"的接口)。

9.3 file-like 对象

  • 类文件对象,可以像文件对象一样操作
  • socket 对象、输入输出对象(stdin、stdout)、标准错误(stderr)… 都是类文件对象。

这几个文件在程序运行中,默认一直是打开的

  • stdin 标准输入 (设备) 文件,比如键盘。
  • stdout 标准输出 (设备) 文件,默认向屏幕输出。
  • stderr 标准错误 (设备) 文件,默认向屏幕输出。

都默认向屏幕输出,那 stdout 和 stderr 区别在哪?

stdout 是行缓冲的,他的输出会放在一个 buffer 里面,只有到换行的时候,才会输出到屏幕。而 stderr 是无缓冲的,会直接输出。

原文勘误:stderr 并非「无缓冲」

Python 3.9 起,sys.stderr 默认是行缓冲(line_buffering=True),不再是"无缓冲直接输出"。本机实测:

sys.stdout.line_buffering = False
sys.stderr.line_buffering = True    ← 行缓冲,不是无缓冲
sys.stderr.write_through  = False

更准确的表述:stderr 是行缓冲(且尽量不走用户态缓冲),保证报错信息能及时刷出;stdout 在重定向到文件/管道时是全缓冲。想强制立刻落盘,显式 flush()(或 print(..., flush=True))最稳。

from sys import stdin, stdout, stderr

# stdin 对象
f_stdin = stdin
print(type(f_stdin))
print(f"是否可读:{f_stdin.readable()}\n是否可写:{f_stdin.writable()}\n指针是否是可移动:{f_stdin.seekable()}")
# 循环输入
# f_stdin.read()
# 文件描述符是 0
print(f"文件描述符:{f_stdin.fileno()}")
print("=" * 40)

# stdout 对象
f_stdout = stdout
print(type(f_stdout))
print(f"是否可读:{f_stdout.readable()}\n是否可写:{f_stdout.writable()}\n指针是否是可移动:{f_stdout.seekable()}")
f_stdout.write('我是stdout')
# 文件描述符是 1
print(f"文件描述符:{f_stdout.fileno()}")
print("=" * 40)

# stderr 对象
f_stderr = stderr
print(type(f_stderr))
print(f"是否可读:{f_stderr.readable()}\n是否可写:{f_stderr.writable()}\n指针是否是可移动:{f_stderr.seekable()}")
f_stderr.write('我是stderr\n')
# 文件描述符是 2
print(f"文件描述符:{f_stderr.fileno()}")
print("=" * 40)
原文勘误:三处注释吞掉了代码

原文把注释和代码粘在了同一行,导致 print(...) 被整行注释掉、根本不会执行:

# 文件描述符是 1print(f"文件描述符:{f_stdin.fileno()}")
# 文件描述符是 1print(f"文件描述符:{f_stdout.fileno()}")
# 文件描述符是 2print(f"文件描述符:{f_stderr.fileno()}")

已拆成「注释 + 独立代码行」。同时勘误:stdin 的文件描述符是 0(原文写 1),stdout 是 1,stderr 是 2。

Pasted-image-20250406152759

10 使用 pickle 序列化

Pasted-image-20250406153426

Python 中,一切皆对象,对象本质上就是一个存储数据的内存块。有时候,我们需要将内存块的数据保存到硬盘上,或者通过网络传输到其他的计算机上。这时候,就需要对象的序列化和反序列化。对象的序列化机制广泛的应用在分布式、并行系统上。

  • 序列化指的是:将对象转化成串行化数据形式,以便可以存储到硬盘或通过网络传输到其他地方。
  • 反序列化是指相反的过程,将读取到的串行化数据转化成对象。

我们可以使用 pickle 模块中的函数,实现序列化和反序列化操作。

序列化我们使用:

# obj 就是要被序列化的对象,file 指的是存储的文件
pickle.dump(obj, file)

# 从 file 读取数据,反序列化成对象
pickle.load(file)

案例:将对象序列化到文件中

import pickle

with open(r"./data.dat", "wb") as f:
    a1 = "高淇 "
    a2 = 234
    a3 = [20, 30, 40]

    pickle.dump(a1, f)
    pickle.dump(a2, f)
    pickle.dump(a3, f)

Pasted-image-20250406154931

案例:将获得的数据反序列化成对象

import pickle

with open(r"./data.dat", "rb") as f:
    a1 = pickle.load(f)
    a2 = pickle.load(f)
    a3 = pickle.load(f)

    print(a1)
    print(a2)
    print(a3)

Pasted-image-20250406155358

本机实测:dump / load 必须严格配对
反序列化结果:'高淇'  234  [20, 30, 40]
data.dat 大小:48 B      文件前 2 字节(协议标识):b'\x80\x04'

pickle 是流式的:dump() 几次,就必须按同样的顺序 load() 几次,顺序错乱会读到错误的对象或直接抛 UnpicklingError。反序列化时必须用 rb(二进制读)——data.dat 是二进制文件,用文本模式打开会破坏字节流。

原文勘误:Python 3 没有 cPickle

原文 §2 表格与 §10 提到 "pickle 和 cPickle",且 §10 初稿提到 import cPickle。实际:

import cPickle  ->  ModuleNotFoundError(Python 3 已移除)
pickle.Pickler.__module__ = '_pickle'   # C 实现已并入 pickle,默认自动启用

Python 3 中只 import pickle 即可,C 加速版由 _pickle 自动接管,无需也不应再写 cPickle。

11 CSV 文件的操作

csv (Comma Separated Values) 是逗号分隔的文本格式,常用于数据交换、Excel 文件和数据库数据的导入和导出。

与 Excel 文件不同,CSV 文件中:

  • 值没有类型,所有值都是字符串
  • 不能指定字体颜色等样式
  • 不能指定单元格的宽高,不能合并单元格
  • 没有多个工作表
  • 不能嵌入图像图表

Python 标准库的模块 csv 提供了读取和写入 csv 格式文件的对象。

我们在 Excel 中建立一个简单的表格:

Pasted-image-20250406161513

另存为 csv(逗号分隔),再打开查看这个 csv 文件的内容:

姓名,年龄,工作,薪水
小龙女,20,程序员,50000
赵敏,18,测试工程师,20000
周芷若,19,人工智能开发,50000

Pasted-image-20250406161743

11.1 csv.reader 对象和 csv 文件读取

案例:csv.reader 对象从 csv 文件读取数据

用 utf-8 编码读取(open(r"./test.csv", encoding='utf-8'))时,会打印如下结果:

Pasted-image-20250406162547

原因分析

为什么第一格多了 \ufeff

Excel "另存为 CSV" 会在文件开头写入 BOM(EF BB BF,解码后即 \ufeff)。用 encoding='utf-8' 读时这个字符会粘在第一个字段前面(看着像 姓名 前面有个怪符号);换成 encoding='utf-8-sig' 就会自动吃掉 BOM。

utf-8     读第一格 -> '\ufeff姓名'
utf-8-sig 读第一格 -> '姓名'
import csv

with open(r"./test.csv", encoding='utf-8-sig') as a:
    # 创建 csv 对象,它是一个包含所有数据的列表,每一行为一个元素
    a_csv = csv.reader(a)
    # 获得列表对象,包含标题行的信息
    headers = next(a_csv)
    print(headers)

    for row in a_csv:  # 循环打印各行内容
        print(row)

Pasted-image-20250406162243

11.2 csv.writer 对象和 csv 文件写入

案例:csv.writer 对象写一个 csv 文件

import csv

headers = ["工号", "姓名", "年龄", "地址", "月薪"]
rows = [("1001", "赵敏", 18, "西三旗 1 号院", "50000"),
        ("1002", "周芷若", 19, "西三旗 1 号院", "30000")]

with open(r"./test.csv", "w") as b:
    # 创建 csv 对象
    b_csv = csv.writer(b)
    # 写入一行(标题)
    b_csv.writerow(headers)
    # 写入多行(数据)
    b_csv.writerows(rows)

Pasted-image-20250406163856

原文勘误:Windows 上写 CSV 会多出空行

上面 open(r"./test.csv", "w") 没写 newline=""。在 Windows 上,csv 自己会写 \r\n,而文本模式又会把 \n 翻译成 \r\n,两层叠加变成 \r\r\n,于是每行之间多一个空行。实测:

无 newline="" 的原始字节:b'a,b\r\r\n1,2\r\r\n'   ← 多了一个 \r
有 newline="" 的原始字节:b'a,b\r\n1,2\r\n'       ← 正确

正确写法(官方推荐):

with open(r"./test.csv", "w", encoding="utf-8", newline="") as b:
    b_csv = csv.writer(b)
    b_csv.writerow(headers)
    b_csv.writerows(rows)

12 os 和 os.path 模块

Pasted-image-20250406172405

os 模块让我们能直接操作操作系统:调用系统可执行文件与命令、操作文件和目录等,是系统运维的核心基础。

注意

os 模块是做系统运维非常重要的基础。

os 模块详解

12.1 os 模块——调用操作系统命令

os.system 可以帮助我们直接调用系统的命令

案例:os.system 调用 windows 系统的记事本程序

import os

os.system("notepad.exe")

案例:os.system 调用 windows 系统中 ping 命令

import os

os.system("ping www.baidu.com")

Snipaste_2022-07-03_15-18-58

注意

Linux 上命令行操作更常见,用 os.system 调用相关命令也更方便。

注意

控制台输出中文可能会有乱码问题,可以在 file-->setting 中设置:

Snipaste_2022-07-03_15-22-07

os.startfile 直接调用可执行文件

os.startfile(path) 相当于双击 path:

示例:运行安装好的微信

import os

os.startfile(r"D:\software\WeChat\WeChat.exe")

Snipaste_2022-07-03_15-32-46

示例:path 为图片时相当于双击该图片,会用当前默认的看图工具打开它。例如打开 ly.jpeg

import os

# path 为一张图片相当于双击 ly.jpeg,
# 会使用当前默认的看图工具打开 ly.jpeg 图片
os.startfile("ly.jpeg")

Pasted-image-20250406180743

os.startfile 和 os.system 的区别

os.system(command) 等价于在控制台输入 command 并回车。例如想执行 pip install pkg,直接 os.system("pip install pkg") 即可。而且在 Windows 中,如果在控制台输入一个文件路径,二进制文件会被自动执行;非二进制文件则会自动尝试用相关可执行程序打开。所以 os.system 完全可以实现 os.startfile 的功能,只不过 os.system 还可以执行命令。

示例:os.system 方法:

import os

# 等待 notepad 窗口退出后再继续执行
# (主进程打开新的窗口,原窗口进入休眠状态,待新的窗口关闭,主进程再重新唤醒原窗口)
os.system("notepad.exe")
print('已经退出了notepad')

Pasted-image-20250406174649

关闭记事本窗口:

Pasted-image-20250406174716

注意

我们首先打开了一个记事本,此时程序就阻塞在这里了,如果不把记事本关掉,那么第二个 os.system 是不会执行的;如果想不阻塞,那么只需要多加上一个 start 即可:os.system("start notepad.exe") 。

import os

# 等待 notepad 窗口退出后再继续执行
# (主进程打开新的窗口,原窗口进入休眠状态,待新的窗口关闭,主进程再重新唤醒原窗口)
# 如果想不阻塞,那么只需要多加上一个 start 即可
os.system("start notepad.exe")
print('不等待notepad退出')

Pasted-image-20250406175555

示例:os.startfile 方法:

import os

# 打开窗口后不等待窗口退出直接继续执行
# (主进程创建一个子进程去打新的窗口,主进程创建完成子进程后立即继续往下执行)
os.startfile("notepad.exe")
print('不等待notepad退出直接继续执行')

Pasted-image-20250406174920

原文勘误:os.system / os.startfile 参数不要加引号

原文曾写成 os.system('"notepad.exe"') 和 os.startfile('"notepad.exe"')——字符串里又套了一层双引号,会把引号当成命令/文件名的一部分。实测:

os.system('"notepad.exe"')  ->  cmd 收到 "notepad.exe"(含引号)
                                报错:'"notepad.exe"' 不是内部或外部命令
os.startfile('"notepad.exe"') ->  os.startfile 不做 shell 解析,会把带引号的名字当文件找 → 报错

正确写法就是直接传名字:os.system("notepad.exe") / os.startfile("notepad.exe")。需要延时参数时(如 os.system("start notepad.exe"))也用普通字符串,不要额外套引号。

12.2 os 模块——文件和目录操作

通过前面讲的文件对象即可实现文件内容的读写;若还需要对文件和目录做其他操作,可以使用 os 和 os.path 模块。

os 模块下常用 操作文件 的方法:

方法名 描述
remove(path) 删除指定的文件
rename(src, dest) 重命名文件或目录
stat(path) 返回文件的所有属性
listdir(path) 返回 path 目录下的文件和目录列表

os 模块下关于 目录操作 的相关方法,汇总如下:

方法名 描述
mkdir(path) 创建目录
makedirs(path1/path2/path3/…) 创建多级目录
rmdir(path) 删除目录
removedirs(path1/path2…) 删除多级目录
getcwd() 返回当前工作目录:current work dir
chdir(path) 把 path 设为当前工作目录
walk(top, topdown=True, onerror=None, followlinks=False) 递归方式遍历目录
sep 当前操作系统所使用的路径分隔符
原文勘误:walk() 签名是 Python 2 的老写法

原文表格写作 walk(top, func, arg),这是 Python 2 的签名。Python 3 已改为:

os.walk(top, topdown=True, onerror=None, followlinks=False)

返回值也从"回调"改成了生成器(见 §12.4)。表格已更正。

示例:os 模块获取文件和文件夹相关的信息

import os

#############获取文件和文件夹相关的信息################
# 返回当前操作系统的信息
# windows->nt  linux 和 unix->posix
print(f"当前操作系统的信息:{os.name}")
# 返回当前操作系统的分隔符
# windows->\ linux 和 unix->/
print(f"当前操作系统的分隔符:{os.sep}")
# 返回当前系统的换行符
# windows->\r\n linux-->\n\
print(f"当前系统的换行符:{repr(os.linesep)}")

"""
# 返回文件的信息
st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。
          在某些系统上(如Unix)是最新的元数据更改的时间,
          在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。
"""
print(f'文件的信息:{os.stat("ly.jpeg")}')

Pasted-image-20250406223240

示例:os 模块关于工作目录的操作

import os

##############关于工作目录的操作###############
# 返回当前的工作目录
print(f"当前工作目录:{os.getcwd()}")
# 改变当前的工作目录为:d:盘根目录
print("改变工作目录...")
os.chdir("D:\\")
print(f"当前工作目录:{os.getcwd()}")
# 创建目录
print("创建目录<书籍>...")
os.mkdir("书籍")
print("再次创建目录<书籍>...")
# 创建同名目录会报文件已经存在错误
os.mkdir("书籍")  # FileExistsError

Pasted-image-20250406223142

示例:os 模块创建目录、创建多级目录、删除

单级目录的创建和删除:

# 创建目录<书籍>
os.mkdir("书籍")
# 相对路径都是相对于当前的工作目录
os.rmdir("书籍")

Pasted-image-20250406224212

删除带文件的单级目录:

# 删除带文件的单级目录
os.rmdir("书籍")

Pasted-image-20250406224521
Pasted-image-20250406224545

注意

rmdir 只能删除空目录

多级目录的创建和删除

# 创建多级目录
os.makedirs("电影/港台/周星驰")
# 删除多级目录,只能删除空目录
os.removedirs("电影/港台/周星驰")
# 使用相对路径创建多级目录 ../指的是上一级目录
os.makedirs("../音乐/香港/刘德华")

Pasted-image-20250406224834

注意

removedirs 和 rmdir 一样只能删除空目录

目录重命名

os.makedirs("电影/港台/周星驰")
# 重命名
os.rename("电影","movie")

Pasted-image-20250406225359

列出 path 目录下的文件和目录

# 列出 path 目录下的文件和目录
dirs = os.listdir("movie")
print(dirs)

Pasted-image-20250406225746

注意

listdir 不会列出子文件夹中的内容

12.3 os.path 模块

os.path 模块提供了目录相关(路径判断、路径切分、路径连接、文件夹遍历)的操作

方法 描述
isabs(path) 判断 path 是否绝对路径
isdir(path) 判断 path 是否为目录
isfile(path) 判断 path 是否为文件
exists(path) 判断指定路径的文件是否存在
getsize(filename) 返回文件的大小
abspath(path) 返回绝对路径
dirname(p) 返回目录的路径
getatime(filename) 返回文件的最后访问时间
getmtime(filename) 返回文件的最后修改时间
join(path, *paths) 连接多个 path
split(path) 对路径进行分割,以列表形式返回
splitext(path) 从路径中分割文件的扩展名

示例:获得目录、文件基本信息

#################获得目录、文件基本信息###################
import os
import os.path

# 是否绝对路径
print(r'path D:\a.txt 是否为绝对路径:{}'.format(os.path.isabs(r"D:\a.txt")))

# 是否目录
print(r'path D:\a.txt 是否为目录:{}'.format(os.path.isdir(r"D:\a.txt")))
# path 不存在,返回 False
print(r'不存在的path D:\b 是否为目录:{}'.format(os.path.isdir(r"D:\b")))
# path 存在,返回 True
print(r'存在的path D:\software 是否为目录:{}'.format(os.path.isdir(r"D:\software")))

# 是否文件
print(r'不存在的path D:\a.txt 是否为文件:{}'.format(os.path.isfile(r"D:\a.txt")))
print(r'存在的path b.txt 是否为文件:{}'.format(os.path.isfile(r"b.txt")))

# 文件是否存在
print(r'path D:\a.txt 是否存在:{}'.format(os.path.exists(r"D:\a.txt")))

# 文件大小,单位为 B(字节)
try:
    print(r'不存在path D:\a.txt 文件的大小:{}'.format(os.path.getsize(r"D:\a.txt")))
except FileNotFoundError as e:
    print(r'查看不存在path D:\a.txt 文件的大小报错——FileNotFoundError')
    print(e)
print(r'存在path b.txt 文件的大小:{}'.format(os.path.getsize(r"b.txt")))

# 输出绝对路径
print(r'获取不存在path a.txt 文件的绝对路径:{}'.format(os.path.abspath(r"a.txt")))
print(r'获取存在path b.txt 文件的绝对路径:{}'.format(os.path.abspath(r"b.txt")))

# 输出所在目录
print(r'获取不存在path a.txt 文件所在目录:{}'.format(os.path.dirname(r"a.txt")))
print(r'获取不存在path D:/a.txt 文件所在目录:{}'.format(os.path.dirname(r"D:/a.txt")))

Pasted-image-20250407090713

os.path.dirname("a.txt") 会返回空字符串
os.path.dirname("a.txt")   ->  ''          ← 仅文件名、没有目录部分时返回空串
os.path.dirname("D:/a.txt") ->  'D:/'

这一点和 os.path.abspath() 搭配时要注意:dirname 只做纯字符串切分,不会去补全路径。

示例:获得创建时间、访问时间、最后修改时间

########获得创建时间、访问时间、最后修改时间##########
import os
import os.path

# 返回创建时间
# 获取不存在文件的创建时间
try:
    print(r'获取不存在path D:/a.txt 文件创建时间:{}'.format(os.path.getctime(r"D:/a.txt")))
except FileNotFoundError as e:
    print(r'获取不存在path D:/a.txt 文件创建时间报错——FileNotFoundError')
    print(e)
# 获取存在文件的创建时间
print(r'获取存在path b.txt 文件创建时间:{}'.format(os.path.getctime(r"b.txt")))

# 返回最后访问时间
# 获取不存在文件的最后访问时间
try:
    print(r'获取不存在path D:/a.txt 文件最后访问时间:{}'.format(os.path.getctime(r"D:/a.txt")))
except FileNotFoundError as e:
    print(r'获取不存在path D:/a.txt 文件最后访问时间报错——FileNotFoundError')
    print(e)
# 获取存在文件的最后访问时间
print(r'获取存在path b.txt 最后访问时间:{}'.format(os.path.getatime(r"b.txt")))

# 返回最后修改时间
# 获取不存在文件的最后修改时间
try:
    print(r'获取不存在path D:/a.txt 文件最后修改时间:{}'.format(os.path.getmtime(r"D:/a.txt")))
except FileNotFoundError as e:
    print(r'获取不存在path D:/a.txt 文件最后修改时间报错——FileNotFoundError')
    print(e)
# 获取存在文件的最后修改时间
print(r'获取存在path b.txt 最后修改时间:{}'.format(os.path.getmtime(r"b.txt")))

Pasted-image-20250407092314

原文勘误:getctime / getatime / getmtime 一字之差

"最后访问时间"那一段里,异常分支用的还是 os.path.getctime(...)(应为 getatime),与下面的 getatime 打印对不上。三个函数各管一件事:

getctime(filename)  创建时间(Windows)/ 元数据变更时间(Linux)
getatime(filename)  最后访问时间
getmtime(filename)  最后修改时间

另外:这三个返回值都是 Unix 时间戳(float 秒),想看得懂要 time.ctime(...) 或 datetime.fromtimestamp(...) 转一下。Windows 上 st_ctime = 创建时间,Linux 上则是"元数据变更时间",跨平台时不要把 ctime 当"创建时间"用。

示例:对路径进行分割、连接操作

################对路径进行分割、连接操作####################
import os
import os.path

# 返回绝对路径-->D:\Project\myPython\learnPython\b.txt
path = os.path.abspath(r"b.txt")
print(r'path b.txt 的绝对路径:{}'.format(path))

# 返回二元组:目录、文件-->('D:\\Project\\myPython\\learnPython', 'b.txt')
print(r'D:\Project\myPython\learnPython\b.txt 使用 os.path.split 的结果:{}'
      .format(os.path.split(path)))

# 返回二元组:路径、扩展名-->('D:\\Project\\myPython\\learnPython\\b', '.txt')
print(r'D:\Project\myPython\learnPython\b.txt 使用 os.path.splitext 的结果:{}'
      .format(os.path.splitext(path)))

# 返回连接路径:"aa", "bb", "cc"-->aa\bb\cc
print(r'["aa", "bb", "cc"] 使用 os.path.join 的结果:{}'
      .format(os.path.join("aa", "bb", "cc")))

Pasted-image-20250407093727

原文勘误:.format(path) 缺了 {} 占位符 + 续行符后多带空格

① 原文首行是 print(r'path b.txt 的绝对路径'.format(path))——字符串里没有 {},path 被 format 无视了,只会原样打印 path b.txt 的绝对路径。

'path b.txt 的绝对路径'.format('D:\\...\\b.txt')
-> 'path b.txt 的绝对路径'      ← path 被丢弃

② 原文三处 print(...) 行尾写的是 \ 后面还跟了两个空格(' \ )。Python 的续行符 \ 后不允许有任何字符,这样写会直接报语法错误:

SyntaxError: unexpected character after line continuation character

已更正为一个有对比意义的写法:把 .format(...) 放到下一行(print(...) 的括号本身就能续行,无需 \)。

print(r'D:\Project\myPython\learnPython\b.txt 使用 os.path.split 的结果:{}'
      .format(os.path.split(path)))

示例:列出当前目录下所有的 .py 文件,并输出文件名

# 获取当前路径
path = os.getcwd()
# 列出子目录和子文件
file_list = os.listdir(path)

# 方法一:使用 for 循环 + if 判断
# 列出当前目录下所有的.py 文件,并输出文件名
for file in file_list:
    if file.endswith(".py"):
        print(file)

print("##################")
# 方法二:使用列表推导式
file_list = [file for file in file_list if file.endswith(".py")]

for file in file_list:
    print(file)

Pasted-image-20250407094854

12.4 walk() 递归遍历所有文件和目录

os.walk() 方法是一个简单易用的文件、目录遍历器,可以帮助我们高效的处理文件、目录方面的事情。格式如下:

os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])

其中, top:是要遍历的目录。topdown:可选,True,先遍历 top 目录再遍历子目录。

os.walk() 方法:返回一个 walk 生成器,生成器中每个元素是一个三元组 --> (root, dirs, files)

  • root:当前正在遍历的文件夹本身
  • dirs:一个列表,该文件夹中所有的目录的名字
  • files:一个列表,该文件夹中所有的文件的名字

案例:使用 walk() 递归遍历所有文件和目录

import os
import os.path

# 创建两个文件夹做示例
if not os.path.exists(r'./电影/港台/李丽珍'):
    os.makedirs('./电影/港台/李丽珍')
if not os.path.exists(r'./电影/港台/周慧敏'):
    os.makedirs('./电影/港台/周慧敏')

# 存储所有文件的路径
all_files = []

# 获取当前的路径
path = os.getcwd()
# walk 递归遍历
walk_gen = os.walk(path)
print(f"walk_gen 的类型:{type(walk_gen)}")

for root, dirs, files in walk_gen:
    for dirname in dirs:
        dir_path = os.path.join(root, dirname)
        all_files.append(dir_path)
    for filename in files:
        file_path = os.path.join(root, filename)
        all_files.append(file_path)
        # 打印子目录和子文件
for file in all_files:
    print(file)

Pasted-image-20250407102758

walk 返回的是生成器,不是列表
type(os.walk(path))  ->  <class 'generator'>

每个元素是 (root, dirs, files) 三元组,root 是"当前正在遍历的文件夹本身",dirs/files 是它直接包含的子目录名与文件名列表。topdown=True(默认)时父目录先于子目录产出;想知道"某个目录下所有文件",用 walk 比自己写递归稳得多。

顺带一提:walk 不会把根目录自身当成一个条目,只产出"目录 → 其内容"的三元组。

13 shutil 模块 (拷贝和压缩)

shutil 模块是 Python 标准库中提供的,主要用来做文件和文件夹的拷贝、移动、删除等,还可以做文件和文件夹的压缩、解压缩操作。

os 模块提供了对目录或文件的一般操作。shutil 模块作为补充,提供了移动、复制、压缩、解压等操作,这些 os 模块都没有提供。

案例:实现文件的拷贝

import shutil

# copy 文件内容
shutil.copyfile("1.jpeg", "1_copy.jpeg")

Pasted-image-20250407111538

案例:实现递归的拷贝文件夹内容(使用 shutil 模块)

import shutil

# "音乐"文件夹不存在才能用。
shutil.copytree("电影/学习", "音乐", ignore=shutil.ignore_patterns("*.html","*.htm"))

将「电影/学习」下的内容拷贝到「音乐」文件夹,拷贝时忽略所有 html 和 htm 文件。运行结果如下:

Snipaste_2022-07-03_19-22-27

原文潜台词:copytree 的目标目录必须不存在

原文注释里的「"音乐"文件夹不存在才能用」是硬约束——copytree 默认要求目标目录不存在,已存在会直接抛 FileExistsError(Python 3.8+ 可传 dirs_exist_ok=True 放宽)。另外 ignore=shutil.ignore_patterns(...) 是按名字 glob 匹配,*.html 会忽略掉所有层级里名字匹配的项(实测:音乐/ 下只剩 ['c.txt', '子目录'],.html/.htm 均未被复制)。

案例:实现将文件夹所有内容压缩(使用 shutil 模块)

import shutil
import zipfile

# 方法一
# 将"电影/学习"文件夹下所有内容压缩到"音乐2"文件夹下生成 movie.zip
shutil.make_archive("音乐2/movie", "zip", "电影/学习")

# 方法二
# 压缩:将指定的多个文件压缩到一个 zip 文件
z = zipfile.ZipFile("a.zip", "w")
z.write("1.txt")
z.write("2.txt")
z.close()

案例:实现将压缩包解压缩到指定文件夹(使用 shutil 模块)

import shutil
import zipfile

# 解压缩:
z2 = zipfile.ZipFile("a.zip", "r")
# 设置解压的地址
z2.extractall("d:/")
z2.close()
本机实测:make_archive / zipfile 的产物
shutil.make_archive("音乐2/movie", "zip", "电影/学习")
  -> 音乐2/movie.zip(自动补 .zip 后缀)
zipfile.ZipFile("a.zip","w").write(...)  -> a.zip 内含 ['1.txt', '2.txt']
ZipFile("a.zip","r").extractall("./unzip") -> ./unzip 下出现 ['1.txt', '2.txt']

三种方式的分工:copyfile(单文件)、copytree(整目录 + 忽略规则)、make_archive(整目录 → 压缩包);zipfile 则用于精细控制压缩包内有哪些文件、以及解压到指定目录。

14 递归算法

递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是「自己调用自己」——一个使用递归技术的方法,会直接或间接地调用自身。

利用递归可以用简单的程序来解决一些复杂的问题。比如:斐波那契数列的计算、汉诺塔、快排等问题。

递归结构包括两个部分:

  • 定义递归头。解答:什么时候不调用自身方法。如果没有头,将陷入死循环,也就是递归的结束条件。
  • 递归体。解答:什么时候需要调用自身方法。

案例:使用递归求 n!

def factorial(num):
    if num == 1:
        return 1
    return num * factorial(num-1)

res = factorial(10)
print(res)

Snipaste_2022-07-03_19-51-35

执行过程如图所示:

Snipaste_2022-07-03_19-53-29

原文勘误:递归头只判 num == 1 不够

边界条件写在 if num == 1,一旦传入 0(或负数)就永远到不了出口,直接撞栈上限:

factorial(1)  -> 1
factorial(10) -> 3628800
factorial(0)  -> RecursionError: maximum recursion depth exceeded

正确写法应把出口放宽为 num <= 1:

def factorial(num):
    if num <= 1:          # 出口条件放宽,0 和 1 都能正确返回
        return 1
    return num * factorial(num - 1)

递归的缺陷

简单的程序是递归的优点之一。但是递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,所以在使用递归时要慎重。

案例:使用递归算法遍历目录下所有文件

import os
import os.path

all_files = []
# 创建测试目录
if not os.path.exists("电影/港台/张敏"):
    os.makedirs("电影/港台/张敏")

def get_files(path, level=0):
    for file in os.listdir(path):
        # 获取文件的绝对路径
        file_abspath = os.path.join(path, file)
        # 判断是文件还是目录
        if os.path.isdir(file_abspath):
            all_files.append(('\t' * level) + file_abspath)
            get_files(file_abspath, level + 1)

        # 输出文件路径
        all_files.append(('\t' * level) + file_abspath)

path = os.getcwd()
get_files(path)

for i in all_files:
    print(i)

Pasted-image-20250407113509

原文勘误:目录会被重复记录两次

上面这段的 append 出现了两次:目录在 if 分支里 append 一次、循环体末尾又 append 一次;文件则只在末尾 append 一次。实测(电影/ 下有 学习、港台 两个子目录):

条目总数 = 15 | 唯一 = 10     ← 5 个目录项各重复了一次
重复项:'电影\\学习'、'电影\\港台'、'\t电影\\学习\\子目录'、'\t电影\\港台\\其他'、'\t电影\\港台\\张敏'

修正:每个条目只记一次,把 if 分支里的那句去掉即可:

def get_files(path, level=0):
    for file in os.listdir(path):
        file_abspath = os.path.join(path, file)
        all_files.append(('\t' * level) + file_abspath)   # 只在这里记一次
        if os.path.isdir(file_abspath):                   # 目录就继续往下递归
            get_files(file_abspath, level + 1)

修正后实测:条目总数 = 10,唯一 = 10,无重复。此外,遍历目录更推荐直接用 §12.4 的 os.walk()(返回生成器、无需自己维护 all_files 和缩进层级)。

15 术语表(重要英文单词)

15.1 文件与 IO 基础

术语 全拼/说明 释义
IO Input/Output 输入 / 输出;文件操作的统称
file object —— 文件对象,封装了文件读写等操作的句柄
text mode —— 文本模式,处理基本单元是字符
binary mode —— 二进制模式,处理基本单元是字节
buffer —— 缓冲区,数据先攒在内存、满了再落盘
buffering —— 缓冲策略:-1 默认 / 0 关闭(仅二进制)/ 1 行缓冲(仅文本)/ >1 指定大小
flush —— 把缓冲区内容立即写入磁盘(不关闭文件)
encoding —— 编码(utf-8 / gbk / utf-8-sig …),决定字节与字符的映射
BOM Byte Order Mark 字节序标记;Excel 导出的 CSV 常带,读取用 utf-8-sig 可自动去掉
EOF End Of File 文件末尾;读到此处 read() 返回 ''
file descriptor —— 文件描述符,操作系统给打开文件的编号(stdin=0、stdout=1、stderr=2)
file-like object —— 类文件对象,有 read/write/seek 等接口即可当文件用

15.2 文件读写与指针

术语 全拼/说明 释义
read([size]) —— 读 size 个字符(文本)/ 字节(二进制),省略则读全部
readline([limit]) —— 读一行;limit 限定本行读取的字符数
readlines() —— 把每一行作为字符串放入列表返回
write(s) —— 写入字符串
writelines(s) —— 写入字符串列表,不自动加换行符
seek(offset, whence) —— 移动文件指针;whence 0 头 / 1 当前 / 2 尾
tell() —— 返回指针当前位置(文本模式下是不透明 cookie)
truncate([size]) —— 截断文件,只保留指针前 size 字节
close() —— 先 flush 再关闭并释放资源
pointer / cursor —— 文件指针,标记"下一次从哪读/写"
whence —— seek 的基准位置参数(0/1/2)

15.3 模块与工具

术语 全拼/说明 释义
os operating system 操作系统接口模块:命令、目录、环境
os.path —— 路径处理子模块:判断、切分、连接、时间
StringIO —— 内存中的文本缓冲区(类文件对象)
BytesIO —— 内存中的二进制缓冲区(类文件对象)
pickle —— 序列化 / 反序列化模块(dump / load)
serialization —— 序列化:把对象变成可存储/传输的字节流
deserialization —— 反序列化:把字节流还原成对象
CSV Comma Separated Values 逗号分隔值,表格交换的纯文本格式
csv.reader / csv.writer —— csv 读取器 / 写入器
shutil shell utilities 高阶文件操作:拷贝、移动、压缩、解压
zipfile —— zip 压缩包读写模块
stdlib standard library 标准库;os/csv/pickle 均属标准库

15.4 路径与遍历

术语 全拼/说明 释义
sep separator 当前系统的路径分隔符(Windows \、Linux /)
linesep line separator 当前系统的换行符(Windows \r\n、Linux \n)
absolute path —— 绝对路径,从根目录(盘符)开始
relative path —— 相对路径,相对当前工作目录
cwd current working directory 当前工作目录,os.getcwd() 获取
walk —— 递归遍历目录的生成器,产出 (root, dirs, files)
listdir —— 列出目录下的条目(不含子目录内容)
stat —— 文件状态信息(st_size / st_mtime / st_ctime …)
abspath absolute path 把路径转成绝对路径
dirname / basename —— 取路径中的目录部分 / 文件名部分
split / splitext —— 切分路径 / 切分扩展名
recursion —— 递归:函数自己调用自己
recursion base —— 递归头 / 出口条件,终止递归的关键

16 系列导航

Python 基础系列
  • 1.Python 入门
  • 2.编程基础概念
  • 3.Python 中的基础数据类型
  • 4.字符串
  • 5.内建数据结构
  • 6.控制语句
  • 7.函数用法
  • 8.面向对象编程
  • 9.异常和错误
  • 10.文件操作(IO 技术)(本篇)
  • 11.模块 (module)
  • 12.正则表达式 ★★★
  • 13.并发编程
  • 14.网络编程
  • 15.Python操作MySQL

17 参考链接

分类:Python

posted @ 2026-04-11 02:01  挖掘鱼  阅读(25)  评论(0)    收藏  举报