八、读写文件
1、文件与文件路径
- 文件有两个关键属性:文件名和路径。在windows中,根文件夹名为C:\,也称为C:盘,在OS X和Linux中,根文件夹是/。
- 在windows上路径书写使用倒斜杠\作为文件夹之间的分隔符,但在OS X和Linux上使用正斜杠/作为它们的路径分隔符。
- 将单个文件和路径上的文件夹名称的字符串传递给os.path.join()函数,它会返回一个文件路径的字符串,包括正确的路径分隔符。
>>> import os
>>> os.path.join('user','bin','spam') #os.path模块包含许多与文件名和文件路径相关的有用函数
'user\\bin\\spam' #两个\是因为每个\需要由另一个\字符来转义
2、当前工作目录
每个运行程序都有一个当前工作目录(或cwd),所以没有从根文件夹开始的文件名或路径,都假定在当前工作目录下,利用os.getcwd()函数,可获得当前工作路径的字符串,并可以利用os.chdir()改变它。
>>> os.getcwd()
'C:\\Python34'
>>> os.chdir('C:\\Windows\\System32')
>>> os.getcwd()
'C:\\Windows\\System32'
3、绝对路径与相对路径
- 绝对路径:总是从根文件夹开始。
- 相对路径:它相对于程序的当前工作目录。
单个句点.用作文件夹名称时,指“这个目录”,两个句点..意思是父文件夹。相对路径开始处的.\是可选的。
4、文件相关函数
- os.makedirs()创建新文件夹(目录)。会创建所有必要的中间文件夹,目的是确保完整路径名存在。
>>> os.makedirs('C:\\delicious\\walnut\\waffles')
- os.path.abspath(path)将返回参数的绝对路径的字符串,这是将相对路径转换为绝对路径的简便方法。
- os.path.isabs(path),若参数是一个绝对路径则返回True,若是一个相对路径返回False。
- os.path.relpath(path,start)将返回从start路径到path的相对路径的字符串,若没有提供start,就使用当前工作目录作为开始路径。
>>> os.path.abspath('.')
'C:\\Python34'
>>> os.path.abspath('.\\Scripts')
'C:\\Python34\\Scripts'
>>> os.path.isabs('.')
False
>>> os.path.isabs(os.path.abspath('.'))
True
>>> os.path.relpath('C:\\Windows', 'C:\\')
'Windows'
>>> os.path.relpath('C:\\Windows', 'C:\\spam\\eggs')
'..\\..\\Windows'
- os.path.dirname(path)返回一个字符串,包含path参数中最后一个斜杠之前的所以内容。
- os.path.basename(path)返回一个字符串,包含path参数中最后一个斜杠之后的所有内容。
>>> path = 'C:\\Windows\\System32\\calc.exe'
>>> os.path.basename(path)
'calc.exe'
>>> os.path.dirname(path)
'C:\\Windows\\System32'
- 如果同时需要一个路径的目录名称和基本名称,使用os.path.split(),获得这个字符串的元组。也可调用前两个方法,将它们的返回值放在一个元组中,从而得到同样的元组。
>>> calcFilePath = 'C:\\Windows\\System32\\calc.exe'
>>> os.path.split(calcFilePath)
('C:\\Windows\\System32', 'calc.exe')
>>> (os.path.dirname(calcFilePath), os.path.basename(calcFilePath))
('C:\\Windows\\System32', 'calc.exe')
- os.path.split()不接受一个文件路径并返回每个文件夹的字符串的列表,如果要这样需使用split()方法,并根据os.path.sep中的字符串进行分割。
>>> calcFilePath.split(os.path.sep)
['C:', 'Windows', 'System32', 'calc.exe']
OS X 和 Linux 系统上,返回的列表头上有一个空字符串.
- os.path.getsize(path)将返回path参数中文件的字节数。
- os.listdir(path)返回文件名字符串的列表,包含path参数中的每个文件。
>>> os.path.getsize('C:\\Windows\\System32\\calc.exe')
776192
>>> os.listdir('C:\\Windows\\System32')
['0409', '12520437.cpx', '12520850.cpx', '5U877.ax', 'aaclient.dll',
--snip--
'xwtpdui.dll', 'xwtpw32.dll', 'zh-CN', 'zh-HK', 'zh-TW', 'zipfldr.dll']
- os.path.exists(path)检查path参数所指的文件或文件夹是否存在,若是返回True,否则返回False。
- os.path.isfile(path)检查path参数是否是一个文件,若是返回True,否则返回False。
- os.path.isdir(path)检查path参数是否是一个文件夹,若是返回True,否则返回False。
>>> os.path.exists('C:\\Windows')
True
>>> os.path.exists('C:\\some_made_up_folder')
False
>>> os.path.isdir('C:\\Windows\\System32')
True
>>> os.path.isfile('C:\\Windows\\System32')
False
>>> os.path.isdir('C:\\Windows\\System32\\calc.exe')
False
>>> os.path.isfile('C:\\Windows\\System32\\calc.exe')
True
5、文件读写过程
- 纯文本文件:只包含基本文本字符,不包含字体、大小和颜色信息。比如.txt扩展名和.py扩展名的都是纯文本。
- 二进制文件:是所有其他文件类型,诸如字处理文档、PDF、图像、电子表格和可执行程序等。
在python中读写文件有三个步骤:
(1)、调用open()函数,返回一个File对象。
(2)、调用File()对象的read()或write()方法。
(3)、调用File对象的close()方法,关闭该文件。
6、open()函数
用此函数打开文件时,要传入一个字符串路径。
>>> testfile=open("D:\\zz\\pytest.txt")
这将默认文件以只读模式打开,不能写入或修改它。python默认以读模式打开文件,也可以向open()传入字符串'r',作为第二个参数(效果和上述一样)。
7、读取文件内容
如果希望将整个文件内容读取为一个字符串值,使用File对象的read()方法。
>>> content=testfile.read()
>>> content
'Hello Word!'
也可使用readlines()方法,返回一个字符串的列表,列表中的每个字符串就是文本中的每一行。
>>> testfile=open("D:\\zz\\pytest.txt")
>>> testfile.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n', 'And look upon myself and curse my fate,']
每个字符串值都会以一个换行字符\n结束,除了最后一行。
8、写入文件
要写入文件,必须以写模式(覆写原有的文件)即将'w'作为第二个参数传递给open(),或添加模式(在已有文件的末尾添加文本)即将'a'作为第二个参数传递给open()打开文件。
如果传递给open()的文件名不存在,这两种模式都会创建一个新的空文件,在读取或写入文件后调用close()方法,然后才能再次打开该文件。
>>> testfile=open("D:\\zz\\pytest.txt",'w')
>>> testfile.write('hello world!\n')
13
>>> testfile.close()
>>> testfile=open("D:\\zz\\pytest.txt",'a')
>>> testfile.write('bacon is not a vegetable.')
25
>>> testfile.close()
>>> testfile=open("D:\\zz\\pytest.txt")
>>> content=testfile.read()
>>> content
'hello world!\nbacon is not a vegetable.'
>>> testfile.close()
>>> print(content)
hello world!
bacon is not a vegetable.
write()方法不会像print()函数一样在字符串的末尾自动天剑换行符,必须自己添加该字符。
9、用shelve模块保存变量
利用此模块可将python程序中的变量保存到二进制的shelf文件中。
>>> import shelve
>>> shelfile=shelve.open('pytest') #传入的是一个文件名,可以对返回的值的变量进行修改,类似字典
>>> cats=['zonhie','pookie','simon']
>>> shelfile['cats']=cats
>>> shelfile.close()
在windows上运行上述程序,会在当前工作目录下生成3个二进制文件pytest.bak、pytest.dat 和 pytest.dir:
shelf值不必用读/写模式打开,因为它们在打开后,既能读又能写。shelf值也有keys()和values()方法,返回类似列表的值,所以应该将它们传给list()函数,取得列表的形式。
>>> shelfile=shelve.open('pytest')
>>> type(shelfile)
<class 'shelve.DbfilenameShelf'>
>>> shelfile['cats']
['zonhie', 'pookie', 'simon']
>>> list(shelfile.keys())
['cats']
>>> list(shelfile.values())
[['zonhie', 'pookie', 'simon']]
>>> shelfile.close()
创建文件时,如果需要在 Notepad 或 TextEdit 这样的文本编辑器中读取它们,纯文本就非常有用。但是,如果想保存 Python 程序中的数据,就使用 shelve 模块。
10、用pprint.format()函数保存变量
此函数返回的字符串不仅易于阅读,也是语法正确的python代码。假定有一个字典,保存在一个变量中,若希望保存这个变量和它的内容,以便将来使用,此函数将提供一个字符串,可以将它写入.py 文件。该文件成为自己的模块,如果需要使用存储在其中的变量,就可以导入它。
>>> import pprint
>>> cats = [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}]
>>> pprint.pformat(cats)
"[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]"
>>> fileobj=open('mydata.py','w')
>>> fileobj.write('cats='+pprint.pformat(cats)+'\n')
81
>>> fileobj.close()
>>>
>>> import mydata
>>> mydata.cats
[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]
>>> mydata.cats[0]
{'desc': 'chubby', 'name': 'Zophie'}
>>> mydata.cats[0]['name']
'Zophie'
mydata.py中的内容为:
九、组织文件
1、shutil模块
shutil(或称为shell工具)模块中包含一些函数,用于复制、移动、改名和删除文件。
- 复制文件和文件夹
shutil.copy(source,destination)将路径source处的文件复制到路径destination处的文件夹,若destination是一个文件名,将作为被复制文件的新名字。此函数返回一个字符串,表示被复制文件的路径。
>>> import shutil, os
>>> os.chdir('C:\\')
>>> shutil.copy('C:\\spam.txt', 'C:\\delicious')
'C:\\delicious\\spam.txt'
>>> shutil.copy('eggs.txt', 'C:\\delicious\\eggs2.txt')
'C:\\delicious\\eggs2.txt'
shutil.copy()复制一个文件,shutil.copytree()复制整个文件夹,以及它包含的文件夹和文件。
>>> os.chdir('C:\\')
>>> shutil.copytree('C:\\bacon', 'C:\\bacon_backup')
'C:\\bacon_backup'
- 文件和文件夹的移动与改名
shutil.move(source,destination)将路径source处的文件夹移动到路径destination。并返回新位置的绝对路径的字符串。destination也可以指向一个文件名。(很容易覆写文件)
>>> shutil.move('C:\\bacon.txt', 'C:\\eggs')
'C:\\eggs\\bacon.txt'
>>> shutil.move('C:\\bacon.txt', 'C:\\eggs\\new_bacon.txt')
'C:\\eggs\\new_bacon.txt'
以上假定文件夹eggs存在,若C目录下没有此文件夹,则move()会将bacon.txt改名为eggs的文件(没有.txt扩展名的文本文件)。所以构成目的地的文件夹必须已存在。
>>> shutil.move('C:\\bacon.txt', 'C:\\eggs')
'C:\\eggs'
- 永久删除文件和文件夹
(1)os.unlink(path)将删除path处的文件。
(2)os.rmdir(path)将删除path处的文件夹,该文件夹必须为空,其中没有任何文件和文件夹。
(3)shutil.rmtree(path)删除path处的文件夹,包括它包含的所有文件和文件夹。
for filename in os.listdir():
if filename.endswith('.txt'):
os.unlink(filename)
- 用send2trash模块安全删除
此模块是第三方模块,需自行安装然后用import导入。用os和shutil是永久删除,不可恢复,会释放磁盘空间,而send2trash模块会将文件或文件夹发送到计算机的垃圾箱或回收站,不会释放磁盘空间。
>>> import send2trash
>>> baconFile = open('bacon.txt', 'a')
>>> baconFile.write('Bacon is not a vegetable.')
25
>>> baconFile.close()
>>> send2trash.send2trash('bacon.txt')
2、遍历目录树
希望遍历目录树,处理遇到的每个文件用os.walk()函数。此函数传入一个文件夹的路径。在循环的每次迭代中,返回三个值:
(1)当前文件夹名称的字符串。
(2)当前文件夹中子文件夹的字符串的列表。
(3)当前文件夹中文件的字符串的列表。
for folderName, subfolders, filenames in os.walk('C:\\delicious'):
print('The current folder is ' + folderName)
for subfolder in subfolders:
print('SUBFOLDER OF ' + folderName + ': ' + subfolder)
for filename in filenames:
print('FILE INSIDE ' + folderName + ': '+ filename)
print('')
3、用zipfile模块压缩文件
利用zipfile模块中的函数,python程序可以创建和打开(解压)ZIP文件。
- 读取ZIP文件
要读取,首先必须创建一个ZipFile对象(概念上和File对象相似),需调用zipfile.ZipFile()函数,向它传入一个字符串,表示.zip文件的文件名。
>>> import zipfile, os
>>> os.chdir('C:\\') # move to the folder with example.zip
>>> exampleZip = zipfile.ZipFile('example.zip')#此压缩包已存在
>>> exampleZip.namelist()
['spam.txt', 'cats/', 'cats/catnames.txt', 'cats/zophie.jpg']
>>> spamInfo = exampleZip.getinfo('spam.txt')
>>> spamInfo.file_size #原来文件大小
13908
>>> spamInfo.compress_size #压缩后文件大小
3828
>>> exampleZip.close()
ZipFile对象有一个namelist()方法,返回ZIP文件中包含的所有文件和文件夹的字符串的列表,这些字符串可以传递给ZipFile对象的getinfo()方法,返回一个关于特定文件的ZipInfo对象。此对象有file_size和compress_size等属性。
- 从ZIP文件中解压缩
ZipFile对象的extractall()方法从ZIP文件中解压所有文件及文件夹,放到当前工作目录中,也可以传入一个文件夹名称,将文件解压到这个文件夹,若传递的文件夹不存在则会被创建。
>>> os.chdir('C:\\') # move to the folder with example.zip
>>> exampleZip = zipfile.ZipFile('example.zip')
>>> exampleZip.extractall()
>>> exampleZip.close()
extract()方法从ZIP文件中解压单个文件,传递给它的字符串必须匹配namelist()返回的字符串列表中的一个,也可通过传入第二个参数将文件解压到指定文件夹。此方法的返回值是被压缩后文件的绝对路径。
>>> exampleZip.extract('spam.txt')
'C:\\spam.txt'
>>> exampleZip.extract('spam.txt', 'C:\\some\\new\\folders')
'C:\\some\\new\\folders\\spam.txt'
>>> exampleZip.close()
- 创建和添加到ZIP文件
创建压缩ZIP文件,必须以写模式打开ZipFile对象,即传入‘w’作为第二个参数。向ZipFile对象的write()方法传入一个路径,就会压缩该路径所指的文件,将它加到ZIP文件中。
>>> newZip = zipfile.ZipFile('new.zip', 'w')
>>> newZip.write('spam.txt', compress_type=zipfile.ZIP_DEFLATED) #第一个参数代表要添加的文件名,第二个参数是压缩类型参数,可以总是设置成这样
>>> newZip.close()
以上写模式会覆盖ZIP文件中原有的内容,如果希望将文件添加到原有的ZIP文件中,就以添加模式打开ZIP文件。(第二个参数改成‘a’)
十、调试
1、抛出异常
在代码中可以抛出自己的异常,使用raise语句,此语句包含以下部分:
- raise关键字
- 对Exception函数的调用
- 传递给Exception函数的字符串,包含有用的出错信息
>>> raise Exception('This is the error message')
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
raise Exception('This is the error message')
Exception: This is the error message
通常是调用该函数的代码知道如何处理异常,而不是该函数本身。所以一般raise语句在一个函数中,try和except语句在调用该函数的代码中。
def boxprint(symbol,width,height):
if len(symbol)!=1:
raise Exception('symbol msut be a single charater string.')
if width<=2:
raise Exception('width must be greater than 2.')
if height<=2:
raise Exception('height must be greater than 2.')
print(symbol*width)
for i in range(height-2):
print(symbol+(' '*(width-2))+symbol)
print(symbol*width)
for sym,w,h in (('*', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
try:
boxprint(sym,w,h)
except Exception as err: #若boxprint()返回一个Exception对象,这条语句就会将它保存在名为err的变量中。
print('an exception happened:'+str(err)) #Exception对象可以传递给str(),将它转换为一个字符串。
#运行结果:
****
* *
* *
****
OOOOOOOOOOOOOOOOOOOO
O O
O O
O O
OOOOOOOOOOOOOOOOOOOO
an exception happened:width must be greater than 2.
an exception happened:symbol msut be a single charater string.
2、取得反向跟踪的字符串
若python遇到错误,会产生一些错误信息,称为反向跟踪。反向跟踪包含了出错消息、导致该错误的代码行号,以及导致该错误的函数调用的序列。这个序列称为调用栈。
只要抛出的异常没有被处理,python就会显示反向跟踪,也可以调用traceback.format_exc(),得到它的字符串形式。
>>> import traceback
>>> try:
raise Exception('This is the error message.')
except:
errorFile = open('errorInfo.txt', 'w')
errorFile.write(traceback.format_exc())
errorFile.close()
print('The traceback info was written to errorInfo.txt.')
116
The traceback info was written to errorInfo.txt.
反向跟踪文本被写入errorInfo.txt:
3、断言
断言由assert语句执行,若检查失败,就会抛出异常,在代码中,assert语句包含以下部分:
- assert关键字
- 条件(即求值为True或False的表达式)
- 逗号
- 当条件为False时显示的字符串
>>> podBayDoorStatus = 'open'
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
>>> podBayDoorStatus = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
AssertionError: The pod bay doors need to be "open".
断言针对的是程序员的错误,而不是用户的错误。对于那些可以恢复的错误(文件没有找到或用户输了无效数据等),应抛出异常,而不是用assert语句检测。在运行python时传入-O选项可以禁用断言。
4、日志
Python的logging模块很容易创建自定义的消息记录。要启用logging模块,在程序运行时将日志信息显示在屏幕上,需将以下代码复制到程序顶部:
import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s
- %(message)s')
当python记录一个事件的日志时,它会创建一个LogRecord对象,保存关于该事件的信息。logging模块的函数让你指定想看到的LogRecord对象的细节以及希望的细节展示方式。
import logging
logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug('Start of program')
def factorial(n):
logging.debug('Start of factorial(%s%%)' % (n))
total=1
for i in range(n+1):
total*=i
logging.debug('i is '+str(i)+',total is '+str(total))
logging.debug('End of factorial(%s%%)' % (n))
return total
print(factorial(5))
logging.debug('End of program')
以上函数计算一个数的阶乘。想打印日志信息时,使用logging.debug()函数,此函数将调用basicConfig(),打印一行信息,这行信息的格式在basicConfig()函数中指定的,并且包括我们传递给debug()的消息。程序输出如下:
2021-04-27 14:41:20,668 - DEBUG - Start of program
2021-04-27 14:41:20,678 - DEBUG - Start of factorial(5%)
2021-04-27 14:41:20,680 - DEBUG - i is 0,total is 0
2021-04-27 14:41:20,681 - DEBUG - i is 1,total is 0
2021-04-27 14:41:20,684 - DEBUG - i is 2,total is 0
2021-04-27 14:41:20,686 - DEBUG - i is 3,total is 0
2021-04-27 14:41:20,687 - DEBUG - i is 4,total is 0
2021-04-27 14:41:20,689 - DEBUG - i is 5,total is 0
2021-04-27 14:41:20,691 - DEBUG - End of factorial(5%)
0
2021-04-27 14:41:20,696 - DEBUG - End of program
factorial() 函数返回 0 作为 5 的阶乘,这是不对的。for 循环应该用从 1 到 5的数,乘以 total 的值。但 logging.debug() 显示的日志信息表明,i 变量从 0 开始,而不是 1。因为 0 乘任何数都是 0,所以接下来的迭代中,total 的值都是错的。将代码行 for i in range(n +1)改为 for i in range(1,n + 1),再次运行程序。输出结果为:
2021-04-27 14:48:09,754 - DEBUG - Start of program
2021-04-27 14:48:09,760 - DEBUG - Start of factorial(5%)
2021-04-27 14:48:09,761 - DEBUG - i is 1,total is 1
2021-04-27 14:48:09,762 - DEBUG - i is 2,total is 2
2021-04-27 14:48:09,763 - DEBUG - i is 3,total is 6
2021-04-27 14:48:09,763 - DEBUG - i is 4,total is 24
2021-04-27 14:48:09,764 - DEBUG - i is 5,total is 120
2021-04-27 14:48:09,765 - DEBUG - End of factorial(5%)
120
2021-04-27 14:48:09,766 - DEBUG - End of program
日志消息的好处在于可以在程序中想加多少加多少,之后只要加入一次logging.disable(logging.CRITICAL)调用,就可以禁用日志,不像print(),需要手动删除。
5、日志级别
日志级别提供了一种方式,按重要性对日志消息进行分类。
级别 | 日志函数 | 描述 |
---|---|---|
DEBUG | logging.debug() | 最低级别 |
INFO | logging.info() | 记录程序中一般事件的信息或确保一切工作正常 |
WARNING | logging.warning() | 用于表示可能得问题 |
ERROR | logging.error() | 记录错误,它导致程序做某事失败 |
CRITICAL | logging.critical() | 最高级别,表示致命的错误 |
日志消息作为一个字符串,传递给这些函数。向basicConfig()函数传入 logging.DEBUG 作为 level 关键字参数,这将显示所有日志级别的消息。如果只对错误感兴趣。可将 basicConfig() 的 level 参数设置为 logging.ERROR,这将只显示 ERROR和 CRITICAL 消息,跳过 DEBUG、INFO 和 WARNING 消息。
6、禁用日志
logging.disable() 函数可以禁用日志消息,这样就不必进入到程序中,手工删除所有的日志调用。只要向 logging.disable() 传入一个日志级别,它就会禁止该级别和更低级别的所有日志消息。所以,如果想要禁用所有日志,只要在程序中添加 logging. disable(logging.CRITICAL)。
7、将日志记录到文件
除了将日志消息显示到屏幕上,还可以将它们写入文本文件。logging.basicConfig()函数接受filename关键字参数。
import logging
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='
%(asctime)s - %(levelname)s - %(message)s')
日志信息将被保存到 myProgramLog.txt 文件中。