Python是一种流行的解释型和动态类型的编程语言,用于构建网络服务、桌面应用程序、自动化脚本和机器学习项目。程序员在处理基于Python的软件项目时,经常要访问操作系统的文件系统。
例如,我们使用文本文件作为输入,写文本文件作为输出,并经常处理二进制文件。像其他流行的、通用的编程语言一样,Python 也提供了跨平台的文件处理功能。Python 通过几个内置的函数和标准模块提供文件处理功能。
在这篇文章中,我将解释你需要知道的关于Python文件处理的一切,包括。
先决条件
在开始学习本教程之前,确保你已经安装了Python 3解释器。否则,请安装官方发布的最新 Python 解释器。你也可以在你现有的Python项目中使用本教程的代码片段。
在Python中读取文件
作为第一个活动,让我们写一些代码来读取一个文本文件。我们需要首先创建一个文件对象来读取文件。
Python 提供了内置的open
函数来创建一个具有多种模式的文件对象,如读取模式、写入模式等。创建一个名为myFile.txt
的文本文件,并输入以下内容。
Programming languages
C
C++
Python
JavaScript
Go
现在,创建一个名为main.py
的新文件,并添加以下代码片断。
myFile = open("myFile.txt", "r") # or open("myFile.txt")
print(myFile.read())
myFile.close()
上述代码片断的第一行以给定的文件名创建了myFile
文件对象。由于我们通过第二个参数提供了r
标志,所以内置的open
函数使用读取模式创建了一个文件处理程序。
确保在使用该文件后调用close
方法来释放资源。read
方法返回文件内容,所以一旦你执行上面的代码,你将看到内容,如下图所示。
read
方法会一次性读取整个文件。如果你不想一次读完,你可以用read
方法的参数指定一个字节大小。例如,下面的代码片断只读取前11个字节。
myFile = open("myFile.txt", "r")
print(myFile.read(11)) # Programming
myFile.close()
你会看到输出的第一个字("Programming")--因为第一个字有11个字母,在ASCII编码中一个字母的大小等于一个字节。如果你再次打印read(11)
的结果,你会看到接下来的11个字节(" languages\n"),因为文件光标在之前的read(11)
方法调用中移动了11位。你可以通过使用seek
方法将文件光标重新设置到开头,如下例所示。
myFile = open("myFile.txt")
print(myFile.read(11)) # Programming
print(myFile.read(10)) # languages
myFile.seek(0) # Sets file cursor to the beginning
print(myFile.read(11)) # Programming
myFile.close()
在大多数情况下,逐行处理文件内容是很容易的。你不需要自己实现一个面向行的文件读取机制--Python 提供了内置的功能来逐行读取文件。你可以用for-in
循环和readlines
方法逐行读取文件,如下图所示。
myFile = open("myFile.txt", "r")
for line in myFile.readlines():
print(line)
myFile.close()
用for-enumerate
循环可以得到当前的行号,因为readlines
方法将使用列表类型返回行。下面的代码片断将打印带有各自行号的行内容。
myFile = open("myFile.txt", "r")
for i, line in enumerate(myFile.readlines()):
print(i, line) # line number and content
myFile.close()
在 Python 中写文件
早些时候,我们通过使用r
标志在读取模式下创建文件对象。在读模式下,写文件是不可能的,所以我们必须使用写模式 (w
) 来写文件。
也可以使用r+
或w+
标志来同时启用读和写模式;我们将在接下来的例子中使用w+
标志。
为了开始写文件,让我们通过编写一些Python代码将下面的文本输入到当前的myFile.txt
。
Programming languages
Rust
Ruby
TypeScript
Dart
Assembly
使用下面的脚本,用上述内容更新myFile.txt
。
myFile = open("myFile.txt", "w")
content = """Programming languages
Rust
Ruby
TypeScript
Dart
Assembly"""
myFile.write(content)
myFile.close()
在这里,我们使用Python多行字符串语法定义了文本文件内容,并使用write
方法将内容写入文件。确保使用带有w
标志的写模式 - 否则,写操作将失败,出现io.UnsupportedOperation
异常。
有时,我们经常要把新的内容追加到一个现有的文件中。在这些情况下,由于资源消耗较大,读写整个内容并不是一个好办法。相反,我们可以使用追加模式(a
)。
看看下面的代码。它将把一种新的编程语言添加到myFile.txt
中的列表中。
myFile = open("myFile.txt", "a")
myFile.write("\nBash")
myFile.close()
上面的代码片段在现有的文件中添加了一个新的行字符(\n
)和一个新的单词,而没有写入整个文件内容。结果,我们将在我们的编程语言列表中看到一个新条目。试着添加更多的条目,看看会发生什么!
在 Python 中读取文件属性
除了原始的文件内容外,磁盘上的文件将包含一些元数据,或者说文件属性,其中包括像大小、最后修改时间、最后访问时间等。
看看下面的文件代码,它显示文件大小、最后访问时间和最后修改时间。
import os, time
stat = os.stat("myFile.txt")
print("Size: %s bytes" % stat.st_size)
print("Last accessed: %s" % time.ctime(stat.st_atime))
print("Last modified: %s" % time.ctime(stat.st_mtime))
os.stat
函数返回一个具有许多文件属性细节的统计结果对象。这里我们用st_size
来获取文件大小,at_atime
来获取最后一次访问文件的时间戳,st_mtime
来获取最后一次修改的时间戳。统计结果对象可以根据你的操作系统而有所不同。例如,在Windows操作系统上,你可以通过st_file_attributes
键来检索Windows特定的文件属性。
如果你只需要得到文件的大小,你可以使用os.path.getsize
方法而不检索所有的元数据,如下面的代码所示。
import os, time
size = os.path.getsize("myFile.txt")
print("Size: %s bytes" % size)
创建新的Python目录
Python提供了os.mkdir
函数来创建一个单一的目录。下面的代码片段在当前工作目录中创建了myFolder
。
import os
os.mkdir("myFolder")
如果你试图用上面的代码递归地创建多个目录,将会失败。例如,你不能一下子创建myFolder/abc
,因为它需要创建多个目录。在这些情况下,os.makedirs
函数将帮助我们,如下所示。
import os
os.makedirs("myFolder/abc") # Creates both "myFolder" and "abc"
读取Python目录内容
Python 还提供了一个简单的 API,通过os.listdir
函数列出目录内容。下面的代码片段列出了你当前工作目录中的所有文件和目录。
import os
cur_dir = os.getcwd()
entries = os.listdir(cur_dir)
print("Found %s entries in %s" % (len(entries), cur_dir))
print('-' * 10)
for entry in entries:
print(entry)
一旦你执行上述脚本,它将显示你当前目录的条目,如下图所示。
试着从一个不同的目录执行该脚本。然后它将显示那个特定目录的条目,因为我们使用os.getcwd
函数来获得当前工作目录。
有时我们需要递归地列出目录内容。os.walk
函数可以帮助我们进行递归目录列表。下面的代码以递归方式列出了当前工作目录的所有条目。
import os
cur_dir = os.getcwd()
for root, sub_dirs, files in os.walk(cur_dir):
rel_root = os.path.relpath(root)
print("Showing entries of %s" % rel_root)
print("-" * 10)
for entry in sub_dirs + files:
print(entry)
os.walk
函数内部有一个递归实现。它为每个条目返回三个值。
- 根目录
- 子目录
- 文件条目
这里我们分别使用root
,sub_dirs
, 和files
变量,用一个for-loop来捕获所有条目。
在Python中删除文件或目录
我们可以使用os.remove
函数来删除一个文件。可以在os.remove
之前使用os.path.exists
函数,以防止出现异常。看看下面的示例代码片断。
import os
file_to_remove = "myFile.txt"
if os.path.exists(file_to_remove):
os.remove(file_to_remove)
else:
print("%s doesn't exist!" % file_to_remove)
Python 标准库也提供了os.rmdir
函数来删除单个目录。它的行为类似于os.mkdir
,如果特定的目录有一些条目,它就不会删除一个目录。首先,尝试用下面的代码来删除一个单一的目录。
import os
dir_to_remove = "myFolder"
if os.path.exists(dir_to_remove):
os.rmdir(dir_to_remove)
else:
print("%s doesn't exist!" % dir_to_remove)
如果myFolder
包含子文件夹或文件,上述代码将抛出一个错误。使用下面的代码片段来递归地删除一个目录。
import os, shutil
dir_to_remove = "myFolder"
if os.path.exists(dir_to_remove):
shutil.rmtree(dir_to_remove) # Recursively remove all entries
else:
print("%s doesn't exist!" % dir_to_remove)
在Python中执行文件搜索
当我们使用自动化脚本工作时,有时需要在磁盘上执行文件搜索。例如,程序员经常需要通过他们的Python脚本找到日志文件、图像文件和各种文本文件。在Python中执行文件搜索有几种不同的方法。
- 用
os.listdir
函数查找所有条目,并在for
循环中用if
条件检查每个条目 - 用
os.walktree
函数递归地查找所有条目,并在一个for
循环中用一个if
条件验证每个条目。 - 用
glob.glob
函数查询所有条目,只获得你需要的条目
总的来说,第三种方法对大多数情况来说是最好的,因为它有内置的过滤支持,非常好的性能,并且从开发者那里需要最少的代码(更像Pythonic)。让我们用Python glob模块实现一个文件搜索。
import glob, os
query = "**/*.py"
entries = glob.glob(query, recursive=True)
no_of_entries = len(entries)
if no_of_entries == 0:
print("No results for query: %s" % query)
else:
print("Found %s result(s) for query: %s" % (no_of_entries, query))
print("-" * 10)
for entry in entries:
print(entry)
上面的代码以递归方式列出了当前目录下的所有Python源文件。查询变量中的前两个星号 (**
) 指示 Python 搜索每个子目录,而最后一个星号指的是任何文件名。
运行上述脚本。你会看到Python源文件,如下图所示。
尝试通过改变query
变量来搜索不同的文件类型。
在Python中处理二进制文件
早些时候,我们处理了文本文件。内置的open
函数默认以文本模式 (t
) 创建文件对象。非文本文件,如图像文件、压缩文件和视频文件,不能作为纯文本文件来看--因为没有可读的英文句子的二进制文件。因此,我们必须通过字节级(或位级)处理将二进制文件作为非文本文件处理。
为了开始处理二进制文件,让我们写一个带有一些字节的二进制文件。我们要将以下字节保存到myFile.bin
。
01010000 01111001 01110100 01101000 01101111 01101110
为了简单起见,我们可以分别用以下十进制数值来表示上述字节。
80 121 116 104 111 110
现在,在你的Python源文件中添加以下代码,并执行它来创建二进制文件。
myBinaryFile = open("myFile.bin", "wb") # wb -> write binary
bytes = bytearray([80, 121, 116, 104, 111, 110])
myBinaryFile.write(bytes)
myBinaryFile.close()
在这里,我们向文件对象的write
方法传递了一个字节数组实例。另外,注意我们使用二进制模式 (b
) 来创建文件对象。执行上述代码片段后,用你喜欢的文本编辑器打开新创建的myFile.bin
。你会看到以下结果。
我们已经收到了 "Python "作为输出,因为字节数组的字节代表已知的ASCII字符。例如,80
(01010000
) 在ASCII编码中代表字母P
。尽管我们在二进制文件中保存了可读的文本,但几乎所有的二进制文件都包含不可读的字节流。试着通过一个文本编辑器打开一个图像文件。
现在我们可以在下面的示例代码中看到二进制文件的读取操作。
myBinaryFile = open("myFile.bin", "rb")
bytes = myBinaryFile.read()
print(bytes) # bytearray(b'Python')
print("Bytes: ", list(bytes)) # Bytes: [80, 121, 116, 104, 111, 110]
myBinaryFile.close()
Python用二进制模式的read
方法返回字节。这里我们用bytearray
构造函数将字节转换为bytearray
实例。
创建和提取 Python 归档文件
程序员经常使用基于Python的web应用程序、web服务、桌面应用程序和实用程序的归档文件来一次输出或输入多个文件。例如,如果你正在建立一个基于网络的文件管理器,你可以提供一个功能,让用户通过编程生成的 zip 文件一次下载多个文件。
Python标准库通过shutil
模块提供了归档文件处理的API。首先,让我们用myFolder
'的内容制作一个存档。看看下面的代码。确保在运行代码片段之前创建myFolder
并向其中添加一些文件。
import shutil
output_file = "myArchive"
input_dir = "myFolder"
shutil.make_archive(output_file, "zip", input_dir)
你可以用下面的代码将存档文件提取到myNewFolder
。
import shutil
input_file = "myArchive.zip"
output_dir = "myNewFolder"
shutil.unpack_archive(input_file, output_dir)
复制和移动文件
shutil
模块也提供跨平台的API函数来复制和移动文件。请看下面的例子。
import shutil
# copy main.py -> main_copy.py
shutil.copy("main.py", "main_copy.py")
# move (rename) main_copy.py -> main_backup.py
shutil.move("main_copy.py", "main_backup.py")
# recursive copy myFolder -> myFolder_copy
shutil.copytree("myFolder", "myFolder_copy")
# move (rename) myFolder_copy -> myFolder_backup
# if myFolder_backup exists, source is moved inside folder
shutil.move("myFolder_copy", "myFolder_backup")
print("Done.")
Python文件处理的最佳实践
程序员遵循各种编码实践。同样地,Python 程序员在处理文件时也遵循不同的编码实践。
例如,有些程序员使用 try-finally 块并手动关闭文件处理程序。有些程序员通过省略close
方法的调用让垃圾收集器关闭文件处理程序--这不是一个好的做法。同时,其他程序员使用with
语法来处理文件处理程序。
在这一节中,我将总结一些Python中文件处理的最佳实践。首先,看看下面这段遵循文件处理最佳实践的代码。
def print_file_content(filename):
with open(filename) as myFile:
content = myFile.read()
print(content)
file_to_read = "myFile.txt"
try:
print_file_content(file_to_read)
except:
print("Unable to open file %s " % file_to_read)
else:
print("Successfully print %s's content" % file_to_read)
在这里,我们使用with
关键字来隐含地关闭文件处理程序。另外,我们用一个 try-except 块来处理可能出现的异常。当你在使用 Python 文件处理时,可以确保你的代码有以下几点。
- 永远不要忽视异常--特别是对于长期运行的 Python 进程。然而,对于简单的实用程序脚本来说,忽略异常是可以的,因为未处理的异常会使实用程序脚本停止继续运行。
- 如果你没有使用
with
语法,请确保正确关闭已打开的文件处理程序。Python 垃圾收集器会清理未关闭的文件处理程序,但通过我们的代码关闭文件处理程序以避免不必要的资源使用总是好的。 - 确保在你的代码库中统一文件处理的语法。例如,如果你使用
with
关键字来处理文件,确保在所有处理文件的地方使用相同的语法。 - 当你用多个处理程序读或写时,避免再次重开同一个文件。相反,使用
flush
和seek
方法,如下所示。
def process_file(filename):
with open(filename, "w+") as myFile:
# w+: read/write and create if doesn't exist unlike r+
# Write content
myFile.write("Hello Python!")
print("Cursor position: ", myFile.tell()) # 13
# Reset internal buffer
myFile.flush()
# Set cursor to the beginning
myFile.seek(0)
print("Cursor position: ", myFile.tell()) # 0
# Print new content
content = myFile.read()
print(content)
print("Cursor position: ", myFile.tell()) # 13
file_to_read = "myFile.txt"
try:
process_file(file_to_read)
except:
print("Unable to process file %s " % file_to_read)
else:
print("Successfully processed %s" % file_to_read)
上面的内容首先将一个字符串保存到文件中。之后,它通过重置内部缓冲区再次读取新添加的内容。flush
方法清除了内存中临时保存的数据,因此下次读取时将返回新添加的内容。另外,我们需要使用seek(0)
方法调用来重置光标到开头,因为write
方法将其设置到了结尾。
总结
Python为程序员提供了一个简单的语法。因此,几乎所有的文件操作都很容易实现。但是,Python在标准库的设计上有一些问题,所以有多个API函数来处理同样的事情。因此,你必须根据你的要求选择最合适的标准模块。
另外,与其他流行的编程语言相比,Python是一种缓慢的语言。考虑到这一点,要确保优化你的Python脚本而不使用太多的资源。例如,你可以通过逐行处理大的文本文件来优化性能,而不是一次性处理整个内容。
在本教程中,我们讨论了通用文本文件处理和二进制文件处理。如果你需要处理特定的文件格式,选择一个更好的库或标准模块可能是值得的。例如,你可以使用csv标准模块来处理CSV文件,使用PyPDF2库来处理PDF文件。另外,pickle标准模块可以帮助你用文件存储(和加载)Python数据对象。
The postPython file handling:一个完整的指南出现在LogRocket博客上。