python快速入门学习笔记(进阶篇)二十八:文件操作

  • 原创
  • 作者:程序员三丰
  • 发布时间:2026-05-22 15:01
  • 浏览量:3
Python入门第二十八课,主要是学习了文件操作,文件操作是程序与外部数据持久化的桥梁,实现数据读写与存储,是处理配置、日志等文件的基础。

文件的分类

纯文本文件

  • 读取和存储时,要遵循某种『字符编码』规范(如:UTF-8等)进行编码和解码,最终以『二进制』的形式存储。
  • 『纯文本文件』最终会呈现为:可以直接阅读的文本信息。
  • 常见的『纯文本文件』有:.txt.py.md.html等。

二进制文件

  • 读取和存储时,不涉及字符编码,会按照某种文件格式规范把内容转为『二进制』进行存储。
  • 二进制文件需要由『能够识别其格式的软件』进行解析,最终的呈现形式多种多样(音频、图像、幻灯片等)。
  • 常见的二进制文件有:.mp3.mp4.doc.ppt.jpgpng等。

绝对路径 vs 相对路径

➊ 绝对路径

从文件系统的『根目录』开始,完整描述文件或目录所在的位置。

举例:D:/demo/test/a.txt

➋ 相对路径

以当前『工作目录』为参照,描述目标文件或目录相对于它的位置。

举例:../../a.txt(其中 .. 表示上一级目录)

Python 中操作文件的标准流程

标准流程如下:

⑴ 创建『文件对象』;

⑵ 操作文件(读取、写入等);

⑶ 关闭文件。

文件操作的核心 —— **open()**函数,它最常用的三个参数如下:

file:需要操作的文件路径。

mode:文件的打开模式,可选值:

  • r:读取(默认值)
  • w:写入,并先截断文件
  • x:排它性创建,如果文件已存在,则创建失败
  • a:打开文件用于写入,如果文件存在,则在文件末尾追加内容
  • b:二进制模式
  • t:文本模式(默认值)
  • +:打开用于更新(读取与写入)

encoding:字符编码

读取文件

seek 方法

功能:用于改变文件对象指针的位置。

语法:seek(offset, whence)

参数:

offset偏移量,要移动多少距离。

whence参考点,从哪里开始计算偏移,有三种取值:

  • 0:从文件开头计算(默认值)
  • 1:从当前位置开始
  • 2:从文件末尾开始

注意:在文本模式下,不要随意去定位中文字符的位置,否则可能破坏文件编码。

代码实例:常用场景是将当前指针移动到开始,便于重新开始操作。

file = open('./test/a.txt', 'rt', encoding='utf-8')

# 第一次读取文件
content = file.read()
print('下面是第一次读取到的内容:')
print(content)

# 第二次读取文件
content = file.read()
print('下面是第二次读取到的内容:')
print(content)
print(repr(content)) # 查看读取到的原始数据

file.close()

上面的代码运行结果,发现第二次读取到的内容为空,原因是针对同一个文件对象,第一次读取完针会移动到末尾,接着第二次读取就会读取不到内容。

如果需要第二次也能读取到内容,需要使用seek方法将指针移动开始,实现代码如下:

file = open('./test/a.txt', 'rt', encoding='utf-8')

# 第一次读取文件
content = file.read()
print('下面是第一次读取到的内容:')
print(content)

# 第二次读取文件
file.seek(0)
content = file.read()
print('下面是第二次读取到的内容:')
print(content)

file.close()

read 方法

概述:使用『文件对象』的read()方法,读取文件中的内容。

方法说明:

  • read(size)中的size是可选参数:
    • 若不传递size参数,表示:读取文件中所有的内容(注意内存占用!)。
    • 若传递了size参数,表示:读取文件中指定个数的字符(文本模式),或指定大小的字节(二进制模式)。
  • read会从上一次read的位置继续读取,若到达文件末尾后继续取,文本模式将会返回空字符串'',二进制模式将会返回空字节b''

示例代码:

# 1. 创建文件对象

# 读取文本文件
file = open('./test/a.txt', 'rt', encoding='utf-8')
# 读取二进制文件,下面以图片文件为例(注意:第二个参数mode的值为rb,且不能设置encoding参数)
file = open('./test/b.jpg', 'rb')

# 2. 读取文件

# 已知内容长度,逐步读取文件
r1 = file.read(2)
r2 = file.read(3)
r3 = file.read(4)
r4 = file.read()  # 读取文件所有内容,如果文件很大会撑爆内存
print(r1, end='')
print(r2, end='')
print(r3, end='')
print(r4, end='')

# 重置上下文文件对象指针到开头
file.seek(0)
print('\n' + '--' * 10)

# 通过循环多次read(对内存友好)
while True:
    result = file.read(10)
    if result == '':
        break
    print(result, end='')

# 3. 关闭文件对象
file.close()

readline 方法

概述:使用『文件对象』的readline()方法,读取文件中的一行。

方法说明:

  • readline(size)中的size是可选参数:
    • 若不传递size参数,表示:读取当前这一行所有的内容。
    • 若传递了size参数,表示:表示读取当前行时,最多能读取的字符数,或字节数。
    • 注意:size不是行数。
  • readline方法,也是从上一次位置继续读取,若到达文件末尾后继续取,文本模式将会返回空字符串'',二进制模式将会返回空字节b''

示例代码:

# 1. 创建文件对象
file = open('./test/a.txt', 'rt', encoding='utf-8')

# 2. 逐行读取文件
r1 =file.readline()
r2 =file.readline()
r3 =file.readline()
r4 =file.readline()

print(r1.strip())
print(r2.strip())
print(r3.strip())
print(r4.strip())

file.seek(0)
print('\n' + '--' * 10 + '\n')

while True:
    line = file.readline()
    if line == '':
        break
    print(line.strip())

# 3. 关闭文件对象
file.close()

for 循环遍历文件对象

概述:可以使用 for 循环直接遍历『文件对象』(逐行遍历)。

示例代码:

file = open('./test/a.txt', 'rt', encoding='utf-8')

for line in file:
    print(line.strip())

file.close()

readlines 方法

概述:使用『文件对象』的readlines()方法,一次性按“行”读完,返回一个列表。

方法说明:

  • readlines(hint)中的hint是可选参数:
    • 若不传递hint参数,表示:读取当前文件的所有行。
    • 若传递了hint参数,表示:期望读取的【字符个数 或 字节数】(hint不是行数)。
  • 注意:由于**readlines**是一次性读取文件的所有内容,所以不适合读取体积较大的文件

示例代码:

file = open('./test/a.txt', 'rt', encoding='utf-8')

result = file.readlines()
print(result)

file.close()

最佳实践

概述:更推荐使用with上下文管理器,结合for循环遍历,逐行读取文件。

示例代码:

with open('./test/a.txt', 'rt', encoding='utf-8') as file:
    for line in file:
        print(line.strip())

关于 with

概述:Python 中的with主要用于管理程序中“需要成对出现的操作”,例如:

  • 上锁 / 解锁
  • 打开 / 关闭
  • 借用 / 归还

使用with的目标:

编码者只管做具体的事,“进入”和“离开”的事,让 Python 自动处理。

语法格式:

with 能得到一个上下文管理器的表达式 as 变量:
    具体的事1
    具体的事2
    具体的事3

上下文管理协议:

  • __enter__方法:with中的代码执行【之前】调用,其返回值会赋值给as后的变量。
  • __exit__方法:with中的代码执行【结束后】调用(无论with中是否会出现异常都会调用)。
  • with 中的代码发生异常时,__exit__方法的返回值来控制是否抛出异常:
    • 返回“真”,表示异常已经被处理,异常不会被继续抛出。
    • 返回“假”,表示异常没有被处理,异常会被继续抛出。

测试代码:定义一个Person类,让其实例对象遵循上下文管理器协议

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

    def speak(self):
        print(f'我叫{self.name},今年{self.age}岁了。')

    def __enter__(self):
        print('---- 我是进入的逻辑 ----')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('---- 我是离开的逻辑 ----')
        if exc_type:
            print(f'异常类型:{exc_type}')
            print(f'异常对象:{exc_val}')
            print(f'异常追踪信息:{exc_tb}')
        return True # return True 则不抛出异常,False 则抛出异常

with Person('远方', 41) as person:
    person.speak()
    person.study() # 此行运行时会抛出异常
    print('6666....') # 由于上一行排除异常,该行不会运行

上面的代码运行逻辑:

1、计算with后面的表达式,得到一个『上下文管理器』;

2、调用『上下文管理器』的__enter__()方法,并将其返回值赋值为as后面的变量;

3、执行with所管理的代码块;

4、无论with中的代码,是正常结束还是发生异常,都会自动调用『上下文管理器』的__exit__()方法。

运行结果:

---- 我是进入的逻辑 ----
我叫远方,今年41岁了。
---- 我是离开的逻辑 ----
异常类型:<class 'AttributeError'>
异常对象:'Person' object has no attribute 'study'
异常追踪信息:<traceback object at 0x000001B9A8A6BA80>

写入文件

w 模式

该模式是写入模式,写入前会先截断文件(清空文件),覆盖性写入。

with open('./test/b.txt', 'wt', encoding='utf-8') as f:
    f.write('hello, Python')

x 模式

该模式是排它性创建,如果文件已创建,则创建失败。换言之,x模式等于新建文件然后写入新建的文件。

# 如果 .text/b.txt 文件已经存在,则会报错:FileExistsError: [Errno 17] File exists: './test/b.txt'
with open('./test/b.txt', 'xt', encoding='utf-8') as f:
    f.write('hello, Python')

a 模式

该模式是追加模式,打开文件用于写入,如果文件存在,则在文件末尾追加内容。

import time
with open('./test/b.txt', 'at', encoding='utf-8') as f:
    f.write('\nhello, Python  ' + time.strftime("%Y-%m-%d %H:%M:%S"))

flush 方法

Python 写入文件时,并不是每写一次就立刻落盘,而是先写到『缓冲区』里。

flush方法的作用把缓冲区中的数据立刻写入到文件中。

测试代码:

with open('./test/b.txt', 'at', encoding='utf-8') as f:
    f.write('\nnew data 1')
    f.write('\nnew data 2')
    f.flush()
    time.sleep(10000)
    f.write('\nnew data 3')
    f.write('\nnew data 4')

代码解析:

  • time.sleep(1000)会阻塞with代码块执行,在阻塞结束之前,如果不使用f.flush,通过f.write写入的内容都在『缓冲区』,需要等到阻塞结束,且后续代码执行完,触发了with模块的__exit__方法,才能把『缓冲区』中的数据立刻写入到文件中。
  • 如果在阻塞前调用了f.flush方法,则会将『缓冲区』中的数据立刻写入到文件中。

组合模式

什么是混合模式?

『混合模式』就是在单独的 rwa 定义的『基础模式』后面,增加一个+号“修饰符”,用来开启“读写”能力,形成的rt+wt+at+等就是『混合模式』。

为什么需要组合模式?

只用基础模式,无法在一个已打开的文件对象上同时进行读写:

# 错误示范:读取模式创建的文件对象,想要写入就会报错
f = open('test.txt', 'r', encoding='utf-8') # 只有读能力
# f.write('hello') # 会报错!io.UnsupportedOperation: not writable
f.close()

# 错误示范:写入模式创建的文件对象,想要读取就会报错
f = open('test.txt', 'w', encoding='utf-8') # 只有写能力,且文件被清空了
# content = f.read() # 会报错!io.UnsupportedOperation: not readable
f.close()

用混合模式就能解决上面的问题:

# 读取混合模式
file_r = open('./test/d.txt', 'rt+', encoding='utf-8')
file_r.write('abc')
file_r.seek(0)
content = file_r.read()
print(content)
file_r.close()

# 写入混合模式
file_w = open('./test/d.txt', 'wt+', encoding='utf-8')
file_w.write('test')
file_w.seek(0)
content = file_w.read()
print(content)
file_w.close()

rt+ 模式

r模式可以读取,+模式可以更新(读取或写入),所以rt+模式可读可写。

注意:r模式打开文件后,文件指针在起始位置。

with open('./test/c.txt', 'rt+', encoding='utf-8') as f:
    f.write('hello world')
    f.seek(0) # 上一行代码执行完,文件对象的指针在末尾,要想读取内容,需要将指针移动到开始
    content = f.read()
    print(content)

注意:针对同一个文件对象,每次执行readwrite方法都会移动文件对象的指针,可能会造成不同的读取和写入的结果。

wt+ 模式

w模式可以写入,+模式可以用于更新(读取或写入),所以wt+模式可读可写。

w模式打开文件后,文件指针在起始位置,但write方法执行完成后,指针在文件结束位置。

with open('./test/c.txt', 'wt+', encoding='utf-8') as f:
    f.write('hello world')
    f.seek(0) # 上一行代码执行完,文件对象的指针在末尾,要想读取内容,需要将指针移动到开始
    content = f.read()
    print(content)

注意:针对同一个文件对象,每次执行readwrite方法都会移动文件对象的指针,可能会造成不同的读取和写入的结果。

xt+ 模式

x模式可以写入,+模式可以用于更新(读取或写入),所以xt+模式可读可写。

x模式打开文件后,文件指针在起始位置。

with open('./test/d.txt', 'xt+', encoding='utf-8') as f:
    f.write('hello world')
    f.seek(0) # 上一行代码执行完,文件对象的指针在末尾,要想读取内容,需要将指针移动到开始
    content = f.read()
    print(content)

注意:针对同一个文件对象,每次执行readwrite方法都会移动文件对象的指针,可能会造成不同的读取和写入的结果。

at+ 模式

a模式可以追加内容,+模式可以用于更新(读取或写入),所以at+模式可读可写。

a模式打开文件后,文件指针在结束位置。

with open('./test/d.txt', 'at+', encoding='utf-8') as f:
    f.write('hello world')
    f.seek(0) # 上一行代码执行完,文件对象的指针在末尾,要想读取内容,需要将指针移动到开始
    content = f.read()
    print(content)

注意:针对同一个文件对象,每次执行readwrite方法都会移动文件对象的指针,可能会造成不同的读取和写入的结果。

目录操作

Python 中操作目录需要导入标准库模块os:

import os

下面介绍常见目录操作。

1、os.mkdir(path)创建“单级”目录

注意:如果目录已经存在,会抛出异常。

os.mkdir('demo')

2、os.makedirs(path)创建“多级”目录

注意:如果路径中的所有目录都已经存在,则会抛出异常。

os.makedirs("demo/a/b/c")

3、os.rmdir(path)删除空目录

如果目录不存在,或目录非空,都会抛出异常。

os.rmdir('demo/a/b/c')

4、os.removedirs(path)递归删除空目录

递归删除多级空目录,在成功删除末尾一级目录后,会“向上”尝试把父级目录也删除,直到父目录不是空目录。

os.removedirs('./demo/a/b')

5、os.path.exists(path)判断路径是否存在(文件/目录都算)

print(os.path.exists("./demo/a")) # True
print(os.path.exists("./demo/a.txt")) # True

6、os.path.isdir(path)用于判断路径

具体判断规则:

  • 路径不存在,返回 False;
  • 路径存在,但指向的是文件,返回False;
  • 路径存在,并且是目录,返回True。
print(os.path.isdir('./demo/1/a')) # False
print(os.path.isdir('./demo/a')) # True

7、os.path.isfile(path)判断是否为文件

print(os.path.isfile('./demo/1/a')) # True
print(os.path.isfile('./demo/a')) # False

8、os.scandir(path)扫描指定目录(只扫描一级)

result = os.scandir('./demo')
for item in result:
    print('目录' if item.is_dir() else '文件', item.name)

# 运行结果
# 目录 1
# 目录 a
# 文件 a.txt

9、os.walk(path)递归遍历指定目录(扫描多级目录)

result = os.walk('./demo')
for item in result:
    print(item)

# 运行结果
# ('./demo', ['1', 'a'], ['a.txt'])
# ('./demo\\1', [], ['a'])
# ('./demo\\a', ['b'], [])
# ('./demo\\a\\b', ['c'], [])
# ('./demo\\a\\b\\c', [], [])

10、hutil.rmtree(path)删除有内容的目录(危险操作,谨慎使用)

import shutil
shutil.rmtree('demo')

两个小练习

练习1:将一个二进制文件复制到指定位置

import os

# 源文件
source = './test/eason-chen-backpack.mp3'
# 目标目录
target = './media'

# 如果目标目录不存在,那就去创建
if not os.path.isdir(target):
    os.mkdir(target)

with open(source, 'rb') as f1, open(target + '/test_media.mp3', 'wb') as f2:
    while True:
        # 每次只读取1KB
        data = f1.read(1024)
        # 如果文件读取完毕了,就跳出循环
        if not data:
            break
        # 向目标文件中写入数据
        f2.write(data)
    print('复制完毕!')

练习2:简单登录日志记录

详细需求如下:

  • 用户输入用户名和密码后,程序进行校验:
  • 用户名不存在,提示“用户名未注册”,并记录日志。
  • 用户名存在,但密码错误,提示“密码错误”,并记录日志。
  • 用户名和密码均正确,提示“登录成功”,并记录日志。

代码实现如下:

import time

# 用户数据
users = {
    '张三': '123456',
    '李四': '888888',
    '王五': 'abc123'
}

# 提示输入信息
username = input('请输入用户名:')
password = input('请输入密码:')

# 获取当前时间
now = time.strftime('%Y-%m-%d %H:%M:%S')

# 如果用户名不在users中
if username not in users:
    print('用户名未注册')
    with open('./test/logs.txt', 'at', encoding='utf-8') as f:
        f.write(f'{now} 登录失败(未注册的用户名{username})\n')

# 如果密码不正确
elif users[username] != password:
    print('密码不正确')
    with open('./test/logs.txt', 'at', encoding='utf-8') as f:
        f.write(f'{now} {username} 登录失败(密码输入错误)\n')

# 登录成功
else:
    print('登录成功!')
    with open('./test/logs.txt', 'at', encoding='utf-8') as f:
        f.write(f'{now} {username} 登录成功 \n')
声明:本文为原创文章,51blog.xyz和作者拥有版权,如需转载,请注明来源于51blog.xyz并保留原文链接:https://www.51blog.xyz/article/130.html

文章归档

推荐文章

buildadmin logo
Thinkphp8 Vue3 Element PLus TypeScript Vite Pinia

🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。

热门标签

PHP ThinkPHP ThinkPHP5.1 Go Mysql Mysql5.7 Redis Linux CentOS7 Git HTML CSS CSS3 Javascript JQuery Vue LayUI VMware Uniapp 微信小程序 docker wiki Confluence7 学习笔记 uView ES6 Ant Design Pro of Vue React ThinkPHP6.0 chrome 扩展 翻译工具 Nuxt SSR 服务端渲染 scrollreveal.js ThinkPHP8.0 Mac webman 跨域CORS vscode GitHub ECharts Canvas vue3 three.js 微信支付 PHP全栈开发 Python AI 人工智能 AI生成 工作经验 实战笔记