Python 12 文件操作

第 12 章 文件操作

1. 文件的分类

1.纯文本文件

  1. 读取和存储时,要遵循某种『字符编码』规范(如:UTF-8 等)进行编码和解码,最终以『二进制』的形式存储。

  2. 『纯文本文件』最终会呈现为:可以直接阅读的文本信息。

  3. 常见的『纯文本文件』有:.txt .py .md .html 等。

2.二进制文件

  1. 读取和存储时,不涉及字符编码,会按照某种『文件格式规范』把内容转为『二进制』进行存储。

  2. 二进制文件需要由『能够识别其格式的软件』进行解析,最终的呈现形式多种多样(音频、视频、图像、幻灯片等)。

  3. 常见的二进制文件有:.mp3 .mp4 .doc .ppt .jpg .png 等。

2. 绝对路径 vs 相对路径

1.绝对路径:

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

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

2.相对路径:

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

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

3. Python 中操作文件的标准流程

标准流程成如下:

  1. 创建『文件对象』

  2. 操作文件(读取、写入 等)

  3. 关闭文件

文件操作的核心 —— open函数

open函数最常用的三个参数如下:

  1. file:要操作的文件路径

  2. mode:文件的打开模式

r :读取(默认值)

w :写入,并先截断文件

x :排它性创建,如果文件已存在,则创建失败

a :打开文件用于写入,如果文件存在,则在文件末尾追加内容

b :二进制模式

t :文本模式(默认值)

  • :打开用于更新(读取与写入)
  1. encoding:字符编码

4. 读取文件

1.read方法

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

  • 方法说明

  1. read(size)中的size是可选参数。

?若不传递size参数,表示:读取文件中所有的内容(注意内存占用!)。

?若传递了size参数,表示:读取文件中指定个数的字符,或指定大小的字节。

  1. read会从上一次read的位置继续读取,若到达文件末尾后继续读取,将返回空字符串。
  • 示例代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 第一步:创建『文件对象』
# 完整写法
# file = open(file='a.txt', mode='rt', encoding='utf-8')

# 简写
file = open('a.txt', 'rt', encoding='utf-8')

# 使用绝对路径读取
# file = open('D:/test/atguigu.txt', 'rt', encoding='utf-8')

# 读取二进制文件
# file = open('D:/test/girl.jpg', 'rb')

# 第二步:操作文件(读取)
# 多次调用read去逐步读取文件
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='')

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

# 第三步:关闭文件
file.close()

2.readline方法

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

  • 方法说明

  1. readline(size) 中的size是可选参数。
  • 若不传递size参数,表示:读取当前这一行所有的内容。

  • 若传递了size参数,表示:表示读取当前行时,最多能读取的字符数,或字节数

注意:size不是行数。

  1. readline方法,也是从上一次位置继续读取,若到达文件末尾后继续读取,返回空字符串。
  • 示例代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')

# 第二步:操作文件(读取)
# 依次调用readline逐行读取
r1 = file.readline()
r2 = file.readline()
r3 = file.readline()
r4 = file.readline()
print(r1.strip())
print(r2.strip())
print(r3.strip())
print(r4.strip())

# 通过循环配合readline逐行读取
while True:
 line = file.readline()
 if line == '':
    break
 # print(line.strip())
 print(line, end='')

# 第三步:关闭文件
file.close()

3.for 循环遍历文件对象

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

  • 示例代码

1
2
3
4
5
6
7
8
9
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')

# 第二步:操作文件(读取)
for line in file:
    print(line, end='')

# 第三步:关闭文件
file.close()

4.readlines方法

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

  • 方法说明

  1. readlines(hint) 中的hint是可选参数。
  • 若不传递hint参数,表示:读取当前文件的所有行。

  • 若传递了hint参数,表示:期望读取的【字符个数 或 字节数】(hint不是行数)。

  1. 注意:由于readlines是一次性读取文件的所有内容,所以不适合读取体积较大的文件。
  • 示例代码
1
2
3
4
5
6
7
8
9
# 第一步:创建『文件对象』
file = open('a.txt', 'rt', encoding='utf-8')

# 第二步:操作文件(读取)
result = file.readlines()
print(result)

# 第三步:关闭文件
file.close()

最佳实践

  • 概述:更推荐使用with上下文管理器,结合for循环遍历,逐行读取文件。
1
2
3
with open('a.txt', 'rt', encoding='utf-8') as file:
    for line in file:
        print(line, end='')

5. 关于with

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

  • 打开 / 关闭

  • 借用 / 归还

  • 使用with的目标:编码者只管做具体的事,“进入”和“离开”的事,让 Python 自动处理。

  • 语法格式

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

  • 上下文管理器协议
  1. __enter__ 方法:with中的代码执行【之前】调用,其返回值会赋值给as后的变量。

  2. __exit__ 方法:with中的代码执行【结束后】调用(无论是with中否出现异常都会调用)。

  • 测试代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 定义一个 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

    # 当 with 中的代码发生异常时,__exit__ 方法的返回值规则如下:
    #   ?返回“真”:表示异常【已经】被处理,异常【不会】被继续抛出。
    #   ?返回“假”:表示异常【没有】被处理,异常【会】被继续抛出。
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('-----我是离开的逻辑-----')
        # exc_type  : 异常类型
        # exc_val   : 异常对象
        # exc_tb    : 异常追踪信息
        if exc_type:
            print(f'异常类型:{exc_type}')
            print(f'异常对象:{exc_val}')
            print(f'异常追踪信息:{exc_tb}')
        return True

# 1.计算 with 后面的表达式,得到一个『上下文管理器』。
# 2.调用『上下文管理器』的 __enter__() 方法,并将其返回值赋给 as 后面的变量。
# 3.执行 with 所管理的代码。
# 4.无论代 with 中的代码,是正常结束,还是发生异常,都会自动调用『上下文管理器』的 __exit__ 方法。

with Person('张三', 18) as p1:
    p1.speak()
    # p1.study()
    print(666)

6. 写入文件

1.w模式

  • 概述:w模式是写入模式,写入前会先截断文件(清空文件)。

  • 代码

1
2
with open('b.txt', 'wt', encoding='utf-8') as file:
    file.write('你好')

2.x模式

  • 概述:x模式是排它性创建,如果文件已存在,则创建失败。

  • 代码

1
2
with open('demo.txt', 'xt', encoding='utf-8') as file:
    file.write('你好')

3.a模式

  • 概述:打开文件用于写入,如果文件存在,则在文件末尾追加内容。

  • 代码

1
2
with open('a.txt', 'at', encoding='utf-8') as file:
  file.write('你好')

7. flush方法

  • 概述:Python 写入文件时,并不是每写一次就立刻落盘,而是:先写到“缓冲区”里。

  • flush方法:把缓冲区中的数据,立刻写入到文件中。

  • 测试代码

1
2
3
4
5
6
7
8
import time
with open('demo.txt', 'at', encoding='utf-8') as file:
    file.write('你好1')
    file.write('你好2')
    file.flush()
    time.sleep(10000)
    file.write('你好3')
    file.write('你好4')

8. 组合模式

1.rt+模式

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

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

  • 备注:由于t是默认值,所以rt+中的t可以省略。

  • 代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
with open('a.txt', 'rt+', encoding='utf-8') as file:
    # seek(offset, whence)方法:用于改变文件对象指针的位置,参数说明如下:
    #   offset:偏移量,要移动多少距离
    #   whence:参考点,从哪里开始计算偏移,有三种取值:
    #       0:从文件开头计算(默认值)
    #       1:从当前位置计算
    #       2:从文件末尾计算
    #  注意:在文本模式下,不要随意去定位中文字符位置,否则可能破坏文件编码。
    file.seek(0, 0)
    file.write('你好')

2.wt+模式

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

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

  • 备注:由于t是默认值,所以wt+中的t可以省略。

  • 代码

1
2
3
4
5
with open('a.txt', 'wt+', encoding='utf-8') as file:
    file.write('你好')
    file.seek(0, 0)
    result = file.read()
    print(result)

3.xt+模式

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

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

  • 备注:由于t是默认值,所以xt+中的t可以省略。

  • 代码

1
2
3
4
5
with open('demo3.txt', 'xt+', encoding='utf-8') as file:
    file.write('你好')
    file.seek(0, 0)
    result = file.read()
    print(result)

3.at+模式

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

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

  • 备注:由于t是默认值,所以at+中的t可以省略。

  • 代码

1
2
3
4
5
with open('a.txt', 'at+', encoding='utf-8') as file:
    file.write('你好')
    file.seek(0, 0)
    result = file.read()
    print(result)

9. 目录操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Python 中常见的目录操作如下

import os
import shutil

# 1.os.mkdir(path):创建“单级”目录(如果目录已经存在,则会抛出异常)
os.mkdir('D:/demo')

# 2.os.makedirs(path):创建“多级”目录(如果路径中的所有目录都已经存在,则会抛出异常)
os.makedirs('D:/demo/aa/bb')

# 3.os.rmdir(path):删除空目录(如果目录不存在,或目录非空,都会抛出异常)
os.rmdir('D:/demo/aa/bb')

# 4.os.removedirs(path):递归删除空目录,在成功删除末尾一级目录后,会“向上”尝试把父级目录也删除
# (直到父目录不是空目录)
os.removedirs('D:/demo/aa/bb')

# 5.os.path.exists(path):判断路径是否存在(文件/目录都算)
result = os.path.exists('D:/demo/aa/bb')
print(result)

# 6.os.path.isdir(path):用于判断路径,具体规则如下:
# 1.路径不存在 ==================> 返回 False
# 2.路径存在,但指向的是文件 =====> 返回 False
# 3.路径存在,并且是目录 =======> 返回 True
result = os.path.isdir('D:/demo/aa/bb')
print(result)

# 7.os.path.isfile(path):判断是否为文件
result = os.path.isfile('D:/demo/aa/bb')
print(result)

# 8.os.scandir(path):扫描指定目录
result = os.scandir('D:/demo')
for item in result:
    print('目录' if item.is_dir() else '文件', item.name)

# 9os.walk(path):按层级,递归地遍历指定目录下,所有的子目录和文件
result = os.walk('D:/demo')
for item in result:
    print(item)

# 危险操作:删除有内容的目录
shutil.rmtree('D:/demo')

10. 两个小练习

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import os
# 源文件
source = 'music.mp3'
# 目标目录
target = 'D:/media'

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

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

练习 2:日志记录,需求如下:

  1. 用户输入用户名和密码后,程序进行校验:

  2. 用户名不存在,提示“用户名未注册”,并记录日志。

  3. 用户名存在,但密码错误,提示“密码错误”,并记录日志。

  4. 用户名和密码均正确,提示“登录成功”,并记录日志。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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('log.txt', 'at', encoding='utf-8') as file:
        file.write(f'{now}  {username}  登录失败(用户未注册)\n')

# 如果密码不正确
elif users[username] != password:
    print('密码不正确')
    with open('log.txt', 'at', encoding='utf-8') as file:
        file.write(f'{now}  {username}  密码错误 \n')

# 登录成功
else:
    print('登录成功!')
    with open('log.txt', 'at', encoding='utf-8') as file:
        file.write(f'{now}  {username}  登录成功 \n')
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计