Python文件与上下文管理器
Python相对路径
当前工作目录
file_list = os.listdir('.') #得到当前工作目录中的第一层文件和文件夹
print(file_list)
fp = open('test.txt', 'w') #得到当前工作目录中的名称为'test.txt'的文件
fp = open('./test.txt', 'w') #得到当前工作目录中的名称为'test.txt'的文件
当前工作目录的父目录
file_list = os.listdir('..') #得到当前工作目录的父目录中的第一层文件和文件夹
print(file_list)
fp = open('../test.txt', 'w') #得到当前工作目录的父目录中的名称为'test.txt'的文件
当前工作目录的根目录
写法一
file_list = os.listdir('/') #得到当前工作目录的根目录中的第一层文件和文件夹
print(file_list)
fp = open('/test.txt', 'w') #得到当前工作目录的根目录中的名称为'test.txt'的文件
写法二
file_list = os.listdir('\\') #得到当前工作目录的根目录中的第一层文件和文件夹
print(file_list)
fp = open('\\test.txt', 'w') #得到当前工作目录的根目录中的名称为'test.txt'的文件
open函数
作用
创建一个文件对象(与磁盘上的真实文件一一对应),IO流会将磁盘文件中的内容和程序中的对象中的内容进行同步
返回值
_io.TextIOWrapper类型对象,即被创建的文件对象,为一个迭代器
函数形参
-
file
- 传入一个字符串类型对象,用于指定要打开或者创建的文件的名称
-
mode
- 传入一个字符串类型对象,用于指定模式
- 下面写出了各种模式及其作用,x、r、w、a只能选择一个使用
- r: 读模式(默认),如果文件不存在,则抛出异常;如果文件已存在,则文件指针位于文件开头。只能进行读操作
- w:写模式,如果文件不存在,则创建文件;如果文件已存在,则打开时自动清空原有内容。只能进行写操作
- x:写模式,如果文件不存在,则创建文件;如果文件已存在,则抛出异常。只能进行写操作
- +:读写模式(不可单独使用),其他模式与该模式联合使用时,针对文件对象可以同时允许进行读和写的操作
- a:追加模式,如果文件不存在,则创建文件;如果文件已存在,则不会清空内容,而是文件指针自动移动到文件末尾
- b:二进制模式(不可单独使用),打开文件时不允许指定encoding形参
- t:文本模式(默认,不可单独使用),打开文件时可以指定encoding形参
-
buffering
- 传入一个int类型对象,用于指定读写文件的缓存模式。
- 0:表示不缓存
- 1:表示行缓存(仅限文本模式)
- 大于1:表示缓冲区的大小
- 传入一个int类型对象,用于指定读写文件的缓存模式。
-
encoding
- 传入一个字符串类型对象,用于指定对文本进行编码和解码的方式,只适用于文本模式,可以使用Python支持的任何格式,如GBK、utf8、CP936等等
产生异常的情况
由于指定文件不存在、访问权限不够、磁盘空间不够或者其他原因导致创建文件失败,则会抛出异常
案例
fp = open('test.txt', 'w+') #以读写模式打开,test.txt文件,打开时会自动清空内容
print(fp) #打印文件对象
#输出结果:<_io.TextIOWrapper name='test.txt' mode='w+' encoding='cp936'>
文件对象
文件对象是一个迭代器,可以直接使用for循环进行遍历
属性讲解
- buffer
- 返回当前文件的缓存区对象
- closed
- 判断文件是否关闭,如果已经关闭,返回True
- fileno
- 文件号(不重要)
- mode
- 返回文件的打开模式
- name
- 返回文件名
方法讲解
案例中test.txt文件中的内容为
1.tell()
作用
得到当前的指针相对于文件开始的偏移量(偏移量的单位是字节)
返回值
int类型对象 一个偏移量
案例
with open('test.txt', 'r') as fp: #使用读模式打开文件
print(fp.tell())
#输出结果:0
with open('test.txt', 'a') as fp: #使用写模式打开文件
print(fp.tell())
#输出结果:24
2.seek()
作用
进行文件指针的重定位(定位的单位是字节)。如果参数为正,则指针往后移动;如果参数为负,则指针往前移动
可以指定参考位置,默认为以文件开头作为参考
- 0:从文件头开始计算
- 1:从当前位置开始计算
- 2:从文件尾开始计算
在文本文件中,没有使用b模式(二进制模式)选项打开的文件,只允许将文件头作为参考计算相对位置,从当前位置或者文件尾计算时就会引发异常
返回值
int类型对象 文件指针当前位置
案例
with open('test.txt', 'r') as fp:
print('此时的偏移量为:', fp.tell())
fp.seek(1) #以文件开始处为参考点,向后移动一个字节
print('此时的偏移量为:', fp.tell())
#输出结果:
"""
此时的偏移量为: 0
此时的偏移量为: 1
"""
with open('test.txt', 'r') as fp: #没有使用b模式选项打开
fp.seek(1, 1)
#引发异常:can't do nonzero cur-relative seeks
with open('test.txt', 'r') as fp: #没有使用b模式选项打开
fp.seek(1, 2)
#引发异常:can't do nonzero end-relative seeks
3.read()
作用
对文件进行内容的读取
可以传入参数,来指定所读的最大字符数(针对于文本文件)或者 字节数(针对二进制文件)
如果传入的参数大于实际可以读取的最大数量,则会在文件结尾自动结束读取
如果不传入参数,默认表示读取该文件中的所有内容
值得注意的是,在进行读取的过程中,文件指针也会进行相应的移动
返回值
字符串类型对象 读取到的内容
案例
with open('test.txt', 'r') as fp:
print(fp.read())
#输出结果:
"""
12345678910
01987654321
"""
with open('test.txt', 'r') as fp:
print(fp.read(100))
#输出结果:
"""
12345678910
01987654321
"""
with open('test.txt', 'r') as fp:
print(fp.read(3))
#输出结果:123
with open('test.txt', 'r') as fp:
print('此时的偏移量为:', fp.tell())
print(fp.read(3))
print('此时的偏移量为:', fp.tell())
#输出结果:
"""
此时的偏移量为: 0
123
此时的偏移量为: 3
"""
with open('test.txt', 'r') as fp:
print(fp.read(3))
fp.seek(0) #将文件指针移回文件开始处
print(fp.read(3))
print(fp.read(3))
#输出结果:
"""
123
123
456
"""
4.readline()
作用
针对文件指针所在行的内容(可能是部分)进行读取
可以传入参数,来指定所读的最大字符数(针对于文本文件)或者 字节数(针对二进制文件)
如果传入的参数大于实际的该行的最大可读取数,则会在行尾自动结束读取
如果不传入参数,默认表示读取该行的所有内容
在进行读取的过程中,文件指针也会进行相应的移动
返回值
字符串类型对象 读取到的内容
案例
with open('test.txt', 'r') as fp:
print(list(fp.readline()))
#输出结果:['1', '2', '3', '4', '5', '6', '7', '8', '9', '1', '0', '\n']
with open('test.txt', 'r') as fp:
print(fp.readline(100))
#输出结果:['1', '2', '3', '4', '5', '6', '7', '8', '9', '1', '0', '\n']
with open('test.txt', 'r') as fp:
print(fp.readline(3))
#输出结果:123
with open('test.txt', 'r') as fp:
fp.seek(13)
print(fp.readline())
#输出结果:01987654321
5.readlines()
作用
针对文件指针所在位置的后面各行的内容(可能是部分)进行读取(按照\n分隔每一行的),返回值在必要的时候会自动添加上""来表示转义字符
可以传入参数,来指定所读的最大行数
如果传入的参数大于实际的该行的最大可读取的行数,则会在文件末尾自动结束读取
如果不传入参数,默认表示读取后面各行的所有内容
在进行读取的过程中,文件指针也会进行相应的移动
返回值
列表类型对象 一个元素为字符串(每一行一个字符串)的列表
案例
with open('test.txt', 'r') as fp:
print(fp.readlines())
#输出结果:['12345678910\n', '01987654321']
with open('test.txt', 'r') as fp:
print(fp.readlines(100))
#输出结果:['12345678910\n', '01987654321']
with open('test.txt', 'r') as fp:
print(fp.readlines(1))
#输出结果:['12345678910\n']
with open('test.txt', 'r') as fp:
fp.seek(13)
print(fp.readlines(1))
#输出结果:['01987654321']
6.write()
作用
对文件进行内容写入,传入的必须是字符串类型对象
值得注意的是,在进行写入的过程中,文件指针也会进行相应的移动
返回值
int类型对象 写入文件的字符串的字节数
案例
with open('test.txt', 'w') as fp:
print(fp.write('00\n00'))
#输出结果:5
#文件内容:
"""
00
00
"""
with open('test.txt', 'w') as fp:
print(fp.tell())
fp.write('00\n00')
print(fp.tell())
#输出结果:
"""
0
6
"""
7.writelines()
作用
对文件进行内容写入,传入一个序列,该序列可以是存储字符串的任何可迭代对象,通常是一个字符串列表
在写入内容的过程中,不会自动在各个字符串元素之间添加上换行符
在进行写入的过程中,文件指针也会进行相应的移动
返回值
空类型对象 None
案例
with open('test.txt', 'w') as fp:
fp.writelines(['1', '2', '3'])
#文件内容:123
with open('test.txt', 'w') as fp:
seq = map(str, [1, 2, 3])
fp.writelines(seq)
#文件内容:123
8.seekable()
作用
用来测试当前文件对象是否支持随机访问,如果不支持,则调用文件对象的方法seek()、tell()、truncate()时会出现异常
返回值
布尔类型对象 True 或 False
案例
with open('test.txt', 'w') as fp:
print(fp.seekable())
#输出结果:True
9.flush()
作用
把缓冲区的内容写入文件,但是不关闭文件
返回值
空类型对象 None
案例
fp = open('test.txt', 'w')
fp.flush()
print(fp.closed) #打印文件关闭情况
#输出结果:False
10.close()
作用
把缓冲区的内容写入文件,同时关闭文件,释放文件对象相关资源
返回值
空类型对象 None
案例
fp = open('test.txt', 'w')
fp.close()
print(fp.closed) #打印文件关闭情况
#输出结果:True
上下文管理器
资源关闭
对于系统资源比如文件、数据库连接、socket来说,应用程序打开这些资源并执行完业务逻辑以后,必须要做的一件事就是要关闭(断开)该资源
以Python文件为例,如果不对文件进行关闭,在极端情况下会出现错误,提示有太多打开的文件了,因为操作系统允许你打开的最大文件数目是确定的,即不可能打开无限多个文件
问题
如果正常地使用文件对象的close方法进行文件关闭,可能会因为对文件进行读写操作的过程中出现异常而导致close方法无法被正常调用,资源就一直无法得到释放,之前所作的修改都无法保存
fp = open('test.txt', 'w')
fp.write('msg')
1/0 #在调用文件对象的close方法之前,引发异常
fp.close()
#代码效果:文件'test.txt'中空空如也
解决方法
代码的改进方法就是加上一个异常处理机制,实现异常捕获,使得后续代码可以正常执行,资源可以被正常关闭。但是一种更加简洁、优雅的方法就是使用with关键字,即使用上下文管理器进行资源的自动关闭
上下文 context
所谓的上下文,其实就是环境,比如代码"a += 1",此时将之前的代码"a = 0"称为"上文",后面的代码"print(a)“称为"下文”
a = 0 #上文
a += 1 #作为上下文参照的代码
print(a) #下文
上下文管理器 ContextManager
如果一个类对象定义了特殊方法__enter__和__exit__,则我们称该类对象遵守了上下文管理协议,同时这个类的创建的实例对象就被称为上下文管理器(Python面向对象未学习过的同学,请移步Python——面向对象,__enter__和__exit__方法未学习过的同学,请移步Python——魔法方法)
对于一个上下文管理器,可以使用with语句自动调用该类对象的__enter__和__exit__方法,实现资源的自动开启和关闭(即实现对资源的自动管理)。更为重要的是,可以实现不论因为什么原因(哪怕是代码引发了异常)而跳出with块,总能保证__exit__方法被自动调用(即文件被正常关闭),并且可以在代码块执行完毕后自动还原进入该代码块时的上下文
上下文管理语句 with语句
with语句常用于文件操作、数据库连接、网络连接、多线程与多进程同步时的锁对象管理等场合,在实际开发中,读写文件应优先考虑使用上下文管理语句
在文件读写过程中未引发异常的代码
class File: #一个具有__enter__方法和__exit__方法的类对象
def __init__(self, file_name, mode):
print("对象正在被初始化")
self.file_name = file_name
self.mode = mode
def __enter__(self):
print("enter方法被调用")
self.fp = open(self.file_name, self.mode)
return self.fp
def __exit__(self, *args):
print("exit方法被调用", args)
self.fp.close()
with File('test.txt', 'w') as fp: #使用with语句使用上下文管理器
print("开始进行文件读写操作")
fp.write('123')
#输出结果:
"""
对象正在被初始化
enter方法被调用
开始进行文件读写操作
exit方法被调用 (None, None, None)
"""
#文件内容:123
在文件读写过程中引发异常的代码
class File:
def __init__(self, file_name, mode):
print("对象正在被初始化")
self.file_name = file_name
self.mode = mode
def __enter__(self):
print("enter方法被调用")
self.fp = open(self.file_name, self.mode)
return self.fp
def __exit__(self, *args):
print("exit方法被调用", args)
self.fp.close()
with File('test.txt', 'w') as fp:
print("开始进行文件读写操作")
fp.write('123')
1/0
fp.write('456')
#输出结果:
"""
对象正在被初始化
enter方法被调用
开始进行文件读写操作
exit方法被调用 (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x000002065871F980>)
"""
#引发异常:division by zero
#文件内容:123
代码运行过程解析
- 首先先执行代码"File(‘test.txt’, ‘w’)“,创建了一个上下文管理器,期间自动调用init方法,会打印"对象正在被初始化”
- 然后自动调用该上下文管理器的__enter__方法,会打印"enter方法被调用",并把该方法的返回值赋值给fp
- 接着执行读写代码,会打印"开始进行文件读写操作"
- 最后自动调用该上下文管理器的__exit__方法,会打印"exit方法被调用"
- 如果读写代码运行过程中没有出现异常,最后就会打印(None, None, None)
- 如果读写代码运行过程中出现了异常,会对异常信息进行保存,最后就会打印出那些异常信息了
对于上下文管理语句可以实现即使在触发异常的情况下,依旧可以正常关闭资源的原理
- 实际上是引发异常的时候,内部的__exit__方法得到了异常信息,在执行完毕__exit__方法中的代码后,直接进行了资源关闭,然后按照通常抛异常的方式再抛出这个异常,所以程序引发异常之前所做的所有修改能够进行保存
所以with语句集成了自动打开文件(即 __enter__方法实现了资源的调用),自动关闭文件(即__exit__方法,实现了资源的释放),异常捕获这三种功能
上下文管理器的另一种实现方法——装饰器
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
print("enter方法被调用")
f = open(path, mode)
yield f
print("exit方法被调用")
f.close()
with my_open('test.txt', 'w') as fp:
print("开始进行文件读写操作")
fp.write('123')
#输出结果:
"""
enter方法被调用
开始进行文件读写操作
exit方法被调用
"""
代码运行过程解析
- 首先先执行代码"my_open(‘test.txt’, ‘w’)",调用被装饰的函数my_open,运行其中代码
- 函数调用的内部过程中,会使用生成器,打印"enter方法被调用",使用open函数创建一个文件对象,执行yield语句,返回文件对象f,并赋值给fp
- 接着执行读写代码,会打印"开始进行文件读写操作"
- 最后再次使用生成器,从原来的yield语句继续执行代码,会打印"exit方法被调用" ,调用文件对象的close方法,文件关闭
使用装饰器方式构造的上下文管理器不能捕获异常,较为鸡肋
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
print("enter方法被调用")
f = open(path, mode)
yield f
print("exit方法被调用")
f.close()
with my_open('test.txt', 'w') as fp:
print("开始进行文件读写操作")
fp.write('123')
1/0
fp.write('456')
#输出结果:
"""
enter方法被调用
开始进行文件读写操作
"""
#引发异常:division by zero
#文件内容:文件'test.txt'中空空如也