使用Python做的最常见的任务是读取和写入文件。无论是写入简单的文本文件,读取复杂的服务器日志,还是分析原始的字节数据。所有这些情况都需要读取或写入文件。
在本教程中,你将学习:
本教程主要面向初学者到中级的Python开发者,但是这里有一些提示,更高级的程序员也可以从中获益。
在我们开始研究如何使用Python中的文件之前,了解文件究竟是什么以及现代操作系统如何处理它们的某些方面是非常重要的。
大多数现代文件系统上的文件由三个主要部分组成:
在操作系统上访问文件时,需要文件路径。文件路径是表示文件位置的字符串。它分为三个主要部分:
这是一个简单的例子。假设你有一个位于文件结构中的文件,如下所示:
/│├──path/|││├──to/││└──cats.gif│││└──dog_breeds.txt|└──animals.csv假设您要访问该cats.gif文件,并且你当前的位置与文件夹中path平级。要访问该文件,你需要浏览该path文件夹,然后查看to文件夹,最后到达该cats.gif文件。文件夹路径是path/to/。文件名是cats。文件扩展名是.gif。所以完整的道路是path/to/cats.gif。
现在假设你当前的位置或当前工作目录(cwd)位于我们的示例文件夹结构的to文件夹中。可以通过文件名和扩展名简单地引用文件cats.gif,而不是引用cats.gif完整路径path/to/cats.gif。
/│├──path/|│|├──to/←Yourcurrentworkingdirectory(cwd)ishere|│└──cats.gif←Accessingthisfile|│|└──dog_breeds.txt|└──animals.csv但对于dog_breeds.txt如何进行访问呢?如果不使用完整路径,你将如何访问?你可以使用特殊字符双点(..)来向前移动一个目录。这意味着可以在to目录使用../dog_breeds.txt引用dog_breeds.txt文件。
/│├──path/←Referencingthisparentfolder|│|├──to/←Currentworkingdirectory(cwd)|│└──cats.gif|│|└──dog_breeds.txt←Accessingthisfile|└──animals.csv双点(..)可以连接在一起以遍历当前目录之前的多个目录。例如,在to文件夹中要访问animals.csv,你将使用../../animals.csv。
Pug\r\nJackRusselTerrier\r\nEnglishSpringerSpaniel\r\nGermanShepherd\r\nStaffordshireBullTerrier\r\nCavalierKingCharlesSpaniel\r\nGoldenRetriever\r\nWestHighlandWhiteTerrier\r\nBoxer\r\nBorderTerrier\r\n同样的输出将在Unix设备上以不同方式解释:
Pug\r\nJackRusselTerrier\r\nEnglishSpringerSpaniel\r\nGermanShepherd\r\nStaffordshireBullTerrier\r\nCavalierKingCharlesSpaniel\r\nGoldenRetriever\r\nWestHighlandWhiteTerrier\r\nBoxer\r\nBorderTerrier\r\n这可能会使每行重复出现问题,你可能需要考虑这样的情况。
ASCII实际上是Unicode(UTF-8)的子集,这意味着ASCII和Unicode共享相同的数值字符值。重要的是要注意,使用不正确的字符编码解析文件可能会导致字符转换失败和出错。例如,如果文件是使用UTF-8编码创建的,并且你尝试使用ASCII编码对其进行解析,则如果存在超出这128个值的字符,则会引发错误。
file=open('dog_breeds.txt')打开文件后,接下来要学习的是如何关闭它。
重要的是要记住,关闭文件是你的责任。在大多数情况下,在应用程序或脚本终止时,文件最终将被关闭。但是,无法保证实际上将会发生什么。这可能导致不必要的行为,包括资源泄漏。这也是Python(Pythonic)中的最佳实践,以确保你的代码以明确定义的方式运行并减少任何不需要的行为。
当你操作文件时,有两种方法可以确保文件正确关闭,即使遇到错误也是如此。关闭文件的第一种方法是使用try-finally块:
关闭文件的第二种方法是使用以下with语句:
withopen('dog_breeds.txt')asreader:#Furtherfileprocessinggoeshere使用with语句,一旦离开了with块或甚至在错误的情况下,系统也会自动关闭文件。我强烈建议你尽可能使用with语句,因为它的代码更加清晰并使你更容易处理任何意外错误。
最有可能的是,你也想要使用第二个位置参数mode。此参数是一个字符串,其中包含多个字符以表示你要如何打开文件。默认值和最常见的是'r',表示以只读模式将文件作为文本文件打开:
|模式|含义||----------------|-------------------------------------||'r'|只读模式打开(默认)||‘w’|写入模式打开,会覆盖文件||'rb'或'wb'|以二进制模式打开(使用字节数据读/写)|
让我们回过头来谈谈文件对象。文件对象是:
有三种不同类型的文件对象:
这些中每一种文件类型的都在io模块中定义。这里简要介绍了这三种类型。
文本文件是你将遇到的最常见的文件。以下是一些如何打开这些文件的示例:
open('abc.txt')open('abc.txt','r')open('abc.txt','w')对于此类型的文件,open()将返回一个TextIOWrapper文件对象:
>>>file=open('dog_breeds.txt')>>>type(file)
缓冲二进制文件类型用于读取和写入二进制文件。以下是一些如何打开这些文件的示例:
open('abc.txt','rb')open('abc.txt','wb')对于此类型的文件,open()将返回一个BufferedReader或BufferedWriter文件对象:
>>>file=open('dog_breeds.txt','rb')>>>type(file)
因此通常不使用它。
以下是如何打开这些文件的示例:
open('abc.txt','rb',buffering=0)对于此类型的文件,open()将返回一个FileIO文件对象:
>>>file=open('dog_breeds.txt','rb',buffering=0)>>>type(file)
使用上面使用过的dog_breeds.txt文件,我们来看一些如何使用这些方法的示例。以下是如何使用.read()命令打开和读取整个文件的示例:
>>>withopen('dog_breeds.txt','r')asreader:>>>#Read&printtheentirefile>>>print(reader.read())PugJackRusselTerrierEnglishSpringerSpanielGermanShepherdStaffordshireBullTerrierCavalierKingCharlesSpanielGoldenRetrieverWestHighlandWhiteTerrierBoxerBorderTerrier这是一个如何使用.readline()在一行中每次读取5个字节的示例:
>>>withopen('dog_breeds.txt','r')asreader:>>>#Read&printthefirst5charactersoftheline5times>>>print(reader.readline(5))>>>#Noticethatlineisgreaterthanthe5charsandcontinues>>>#downtheline,reading5charseachtimeuntiltheendofthe>>>#lineandthen"wraps"around>>>print(reader.readline(5))>>>print(reader.readline(5))>>>print(reader.readline(5))>>>print(reader.readline(5))PugJackRusselTerrier译者注:第一次调用reader.readline(5)实际打印出Pug\r\n,因此可以看到有输出一个换行以下是使用.readlines()将整个文件作为列表读取的示例:
>>>f=open('dog_breeds.txt')>>>f.readlines()#Returnsalistobject['Pug\n','JackRusselTerrier\n','EnglishSpringerSpaniel\n','GermanShepherd\n','StaffordshireBullTerrier\n','CavalierKingCharlesSpaniel\n','GoldenRetriever\n','WestHighlandWhiteTerrier\n','Boxer\n','BorderTerrier\n']上面的例子也可以通过使用list()从文件对象创建列表来完成:
>>>f=open('dog_breeds.txt')>>>list(f)['Pug\n','JackRusselTerrier\n','EnglishSpringerSpaniel\n','GermanShepherd\n','StaffordshireBullTerrier\n','CavalierKingCharlesSpaniel\n','GoldenRetriever\n','WestHighlandWhiteTerrier\n','Boxer\n','BorderTerrier\n']迭代文件中的每一行读取文件时常见的事情是迭代每一行。以下是如何使用.readline()执行该迭代的示例:
>>>withopen('dog_breeds.txt','r')asreader:>>>#Readandprinttheentirefilelinebyline>>>line=reader.readline()>>>whileline!='':#TheEOFcharisanemptystring>>>print(line,end='')>>>line=reader.readline()PugJackRusselTerrierEnglishSpringerSpanielGermanShepherdStaffordshireBullTerrierCavalierKingCharlesSpanielGoldenRetrieverWestHighlandWhiteTerrierBoxerBorderTerrier迭代文件中每一行的另一种方法是使用.readlines()文件对象。请记住,.readlines()返回一个列表,其中列表中的每个元素代表文件中的一行:
>>>withopen('dog_breeds.txt','r')asreader:>>>forlineinreader.readlines():>>>print(line,end='')PugJackRussellTerrierEnglishSpringerSpanielGermanShepherdStaffordshireBullTerrierCavalierKingCharlesSpanielGoldenRetrieverWestHighlandWhiteTerrierBoxerBorderTerrier但是,通过迭代文件对象本身可以进一步简化上述示例:
>>>withopen('dog_breeds.txt','r')asreader:>>>#Readandprinttheentirefilelinebyline>>>forlineinreader:>>>print(line,end='')PugJackRusselTerrierEnglishSpringerSpanielGermanShepherdStaffordshireBullTerrierCavalierKingCharlesSpanielGoldenRetrieverWestHighlandWhiteTerrierBoxerBorderTerrier最后的方法更Pythonic,可以更快,更高效。因此,建议你改用它。
现在让我们深入研究文件。与读取文件一样,文件对象有多种方法可用于写入文件:
|方法|描述||----------------|------------------------------------------------------------||.write(string)|将字符串写入文件。||.writelines(seq)|将序列写入文件。不会给每个序列项附加结尾符。这会由你来添加适当的结尾符。|
以下是使用.write()和的简单示例.writelines():
>>>withopen(`dog_breeds.txt`,'rb')asreader:>>>print(reader.readline())b'Pug\n'使用b标志打开文本文件并不那么有趣。假设我们有一张JackRussellTerrier(jack_russell.png)的可爱图片:
|值|描述||--------------|---------------------------------------||0x89|一个“魔术”数字,表示这是一个PNG的开始||0x500x4E0x47|PNG的ASCII||0x0D0x0A|DOS样式行结束\r\n||0x1A|DOS风格的EOF字符||0x0A|一个Unix风格的行结尾\n|
当打开文件并单独读取这些字节时,可以看到这确实是一个.png头文件:
该工具分为三个主要部分。第一个是str2unix()将字符串从\\r\\n行结尾转换为\\n。第二个是dos2unix()将包含\r\n字符的字符串转换为\n。dos2unix()调用str2unix()。最后,有__main__块,只有当文件作为脚本执行时才会调用。
##__file__
这是一个真实的例子。在我过去的一份工作中,我对硬件设备进行了多次测试。每个测试都是使用Python脚本编写的,测试脚本文件名用作标题。然后将执行这些脚本并使用__file__特殊属性打印其状态。这是一个示例文件夹结构:
project/|├──tests/|├──test_commanding.py|├──test_power.py|├──test_wireHousing.py|└──test_leds.py|└──main.py运行main.py产生以下内容:
>>>pythonmain.pytests/test_commanding.pyStarted:tests/test_commanding.pyPassed!tests/test_power.pyStarted:tests/test_power.pyPassed!tests/test_wireHousing.pyStarted:tests/test_wireHousing.pyFailed!tests/test_leds.pyStarted:tests/test_leds.pyPassed!追加文件内容有时,你可能希望追加到文件或在已有文件的末尾开始写入。这可以通过在参数mode中追加'a'字符来完成:
withopen('dog_breeds.txt','a')asa_writer:a_writer.write('\nBeagle')当对dog_breeds.txt再次检查时,你将看到文件的开头未更改,Beagle现在已添加到文件的末尾:
>>>withopen('dog_breeds.txt','r')asreader:>>>print(reader.read())PugJackRusselTerrierEnglishSpringerSpanielGermanShepherdStaffordshireBullTerrierCavalierKingCharlesSpanielGoldenRetrieverWestHighlandWhiteTerrierBoxerBorderTerrierBeagle同时使用两个文件有时你可能想要读取文件并同时写入另一个文件。如果你使用在学习如何写入文件时显示的示例,它实际上可以合并到以下内容中:
__enter__()调用with语句时调用。__exit__()从with语句块退出时被调用。
这是一个可用于制作自定义类的模板:
classmy_file_reader():def__init__(self,file_path):self.__path=file_pathself.__file_object=Nonedef__enter__(self):self.__file_object=open(self.__path)returnselfdef__exit__(self,type,val,tb):self.__file_object.close()#Additionalmethodsimplementedbelow现在你已经拥有了带有上下文管理器的自定义类,你可以与使用内置open()那样使用它:
此外,还有内置库,可以使用它们来帮助你:
还有更多的东西。此外,PyPI还有更多第三方工具可用。一些流行的是以下:
你现在知道如何使用Python处理文件,包括一些高级技术。使用Python中的文件现在比以往任何时候都更容易,当你开始这样做时,这是一种有益的感觉。