一、文件操作

1.1 文件是什么

变量是保存在内存中的,而文件是保存在硬盘上的。像mp4、mp3、jpg、txt、xlsx这些文件中的数据都是保存在硬盘中。硬盘上的数据就是以文件的形式组织。

文件夹(目录)也是一种特殊的文件,称为目录文件。

虽然硬盘上存储的都是文件,但文件里存储数据的内容、格式也是差异很大的。此处重点研究文本文件。

1.2 文件路径

文件夹中可以再包含文件夹。一层一层目录构成的字符串,称为“文件路径”,可以表示文件在硬盘上的具体位置。每个文件的路径都是唯一的。

Windows系统中,目录名之间可以用'\'来分隔,也可以用‘/’,而在其他系统(Linux、苹果)上,只能用‘/’来分隔。

代码中要表示一个文件路径,用‘/’更多,因为‘\’在字符串中表示转义字符。

1.3 文件操作
1.3.1 打开文件

Python中可以使用内建函数open来打开一个文件。

首先我先在电脑D盘PythonProject目录下创建一个test.txt文件,创建之后就可以写代码来打开这个文件了。

f = open('d:/PythonProject/test.txt','r')
print(f)
print(type(f))

其中,第二个参数表示文件的打开方式。r表示read,按照读的方式打开;w表示write,按照写的方式打开;a表示append,也是按照写的方式打开,只不过是把内容写到原有文件内容的末尾。

open的返回值是一个文件对象,我们用变量 f 接收一下。

这个打印出的类型,是Python内部给文件对象起的名字。

文件的内容是在硬盘上的,此处的文件对象则是内存上的一个变量,后续读写文件的操作,都是拿着这个文件对象来进行操作的。可以把文件对象视为一个“遥控器”,可以通过它简介操作硬盘上的内容。计算机中把这样远程操控的“遥控器”称为“句柄(handler)”。

当文件不存在的时候,尝试按照读的方式打开,就会抛出一个“文件没找到”的异常。

1.3.2 关闭文件

文件在使用完之后一定要关闭。

打开文件其实是在申请一定的系统资源。一个系统的资源是有限的,能打开的文件数量也是有限的。

因此,不再使用文件的时候,资源就应该及时释放,否则有可能造成文件资源泄漏,进一步导致其他部分的代码无法顺利打开文件。

我们可以写个代码看看系统最多能打开多少文件。

flist = []
count = 0
while True:
    f = open('d:/PythonProject/test.txt', 'r')
    flist.append(f)
    count += 1
    print(f'打开的文件个数:{count}')

此处显示最多能打开8189个文件。不同的系统可能不一样。而且,可以通过一些设置项来配置能打开文件的最大数目的。

我们发现8189再加3就是8192,是2的13次方,一个很整齐的数字。差了3是因为每个程序在启动的时候都会默认打开三个文件:标准输出、标准输入和标准错误。

这段代码里我们每打开一个文件就把它加到列表里,如果不这么做我们就看不到报错的效果。这是因为Python有一个重要的机制叫做垃圾回收机制(GC),可以自动把不使用的变量进行释放。但是自动释放机制不一定及时,所以不能依赖这个机制。

正确的做法是及时用close关闭文件。

在上述代码中加上一句f.close(),就会一直循环,不会报错了。

1.3.3 写文件

使用write可以实现写文件的操作。

f = open('d:/PythonProject/test.txt', 'w')
f.write('hello')
f.close()

打开文件看看,‘hello’确实被写进去了。

写文件的时候要用‘w’方式打开。如果用‘r’方式打开,会抛出异常。

使用w方式打开,会清空掉文件的原有内容。如果是使用a方式打开,则不会清空,写的内容会追加在原有文件的末尾。

使用a方式:

f = open('d:/PythonProject/test.txt', 'a')
f.write(' world')
f.close()

使用w方式:

f = open('d:/PythonProject/test.txt', 'w')
f.write(' world')
f.close()

写文件的时候如果想要换行,就使用换行符\n即可。

如果文件已经被关闭,意味着系统中和该文件相关的内存资源已经被释放了,强行去写会出异常。

f = open('d:/PythonProject/test.txt', 'w')
f.close()
f.write(' world')

1.3.4 读文件

读文件涉及多种操作。我们先准备好一个文件。

我们使用read来读取文件内容,括号中可以指定读取几个字符,读取的结果用result来接收一下。

f = open('D:/PythonProject/test.txt','r')
result = f.read(5)
print(result)
f.close()

注意,中文字符的编码方式主要有GBK和UTF-8两种。UTF-8是使用更广泛的编码方式,除中文之外还可以表示很多其他语言的字符。

开发的时候需要保证文件内容的编码方式和代码中实际操作文件的编码方式匹配。如果不匹配就会出现报错。比如刚才的代码就报错了。

原因在于这个txt文件编码方式是是UTF-8,而程序是用gbk方式读取的。解决方式就是让程序用UTF-8方式读取。

f = open('D:/PythonProject/test.txt','r',encoding='utf8')
result = f.read(5)
print(result)
f.close()

然而实际开发中,更常见的需求是按行读取。最简单的方式是用for循环。

f = open('D:/PythonProject/test.txt','r',encoding='utf8')
for line in f:
    print(f'line = {line}')
f.close()

每一行后面多了一个空行,是因为本来读到的文件内容中,每行末尾就有换行符,此处用print来打印,又会自动加一个换行符。我们可以给print再多设置一个参数,改变print自动添加换行符的行为。

f = open('D:/PythonProject/test.txt','r',encoding='utf8')
for line in f:
    print(f'line = {line}',end = '')
f.close()

加上这个参数,相当于把print函数末尾自动加的换行符改成了空字符串。

我们还可以使用readlines直接把文件中的所有内容都读取出来,按照行组织到一个列表里。

f = open('D:/PythonProject/test.txt','r',encoding='utf8')
lines = f.readlines()
print(lines)
f.close()

1.4 上下文管理器

如果操作文件的逻辑中有条件语句、函数返回、抛出异常等操作,很容易漏掉关闭文件操作,防不胜防。为了防止这种问题,Python提供了上下文管理器。

def func():
    with open('d:/PythonProject/test.txt', 'r', encoding='utf8') as f:
        # 这里有很多代码
        # 这里有很多代码
        # 这里有很多代码
        # 这里有很多代码
        # 这里有很多代码
        # 这里有很多代码
        if cond:
            return
        # 这里有很多代码
        # 这里有很多代码

这个语句将打开文件的返回值赋给了 f ,同时用with把 f 给监管起来了,当with对应的代码块执行结束,就会自动执行 f 的close。

C++中的智能指针,Java中的try with Resources以及Go lang中的defer都起到类似的效果。

END

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐