Python——文件与上下文管理器(Python相对路径、open函数、文件属性、文件方法、上下文管理器)

本文介绍了Python中如何使用相对路径访问文件,详细讲解了open函数的用法,包括文件模式和文件对象的属性与方法。此外,还探讨了上下文管理器的作用,特别是with语句在确保资源关闭和异常处理方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

路径符号

在我们打开电脑的文件目录的时候,不同的目录之间是用“\”斜杠来区分的,但是直接照搬到Python中是会出错的

Windows系统的文件路径:C:\编程\Python代码

因为Python中有一些特殊符号是通过"“配合特定字母来表示的,即对普通字母进行转义,获得具有特殊含义的符号,比如”\n"表示换行、"\r"表示回车,如果我们的文件路径中恰切出现了这些特殊字符,那么就会出错

解决方法

  • 使用反斜杠来替代斜杠:“C:/sample/ABC/suchas/a.txt”
  • 使用双斜杠来代指斜杠(二次转义):“C:\sample\ABC\suchas\a.txt”
  • 使用r或者R符号来禁止转义:R"C:\sample\ABC\suchas\a.txt"

在Python中,建议使用os.path.join来构建路径,这样可以确保代码在不同操作系统上的兼容性

Python绝对路径

绝对路径指从文件系统的根目录(如Windows系统上的C:\或UNIX/Linux系统上的/)开始的完整路径。绝对路径提供了到达指定文件或目录的完整地址,可以明确无误地指向文件位置,不受当前工作目录的影响

Windows系统:C:\Users\Username\Documents\file.txt
Linux/UNIX系统:/home/username/documents/file.txt

Python相对路径

相对路径基于当前工作目录,不是从根目录开始。使用相对路径时,路径的起点是当前正在访问的文件夹,当前路径是输入运行命令,执行 python 脚本的路径,而不是指 py 文件所在的路径。相对路径更灵活,易于在不同机器或文件夹结构间移植代码,简化文件访问命令

当前工作目录

获取当前工作目录
import os
print(os.getcwd())	#获取当前工作目录,Linux 等系统中查看当前路径的命令是 pwd
被导入模块中的路径问题
# 主脚本test.py是在目录D:\编程\Python代码\test中使用命令执行的

# example_module.py,位于 D:\编程\Python代码 目录下
import os

print("example_module.py代码的相对路径中的 . 表示:", os.path.abspath("."))
print("被导入模块example_module.py中的当前工作目录", os.getcwd())


# test.py,位于 D:\编程\Python代码 目录下
import os
import example_module

print("test.py代码的相对路径中的 . 表示:", os.path.abspath("."))
print("主脚本test.py中的当前工作目录", os.getcwd())

#输出结果:
"""
example_module.py代码的相对路径中的 . 表示: D:\编程\Python代码\test
被导入模块example_module.py中的当前工作目录 D:\编程\Python代码\test
test.py代码的相对路径中的 . 表示: D:\编程\Python代码\test
主脚本test.py中的当前工作目录 D:\编程\Python代码\test
"""
当前工作目录的案例
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函数及文件的访问模式

作用
在 Python 中,可以通过 open()函数来访问和管理文本文件

打开一个文件并创建一个文件对象(与磁盘上的真实文件一一对应),IO流会将磁盘文件中的内容和程序中的文件对象中的内容进行同步,通过文件对象自带的多种函数和方法,可以对被打开的文件执行一系列访问和管理操作

newline

  • 通用换行模式(Universal new line mode),该模式会把所有的换行符(\r \n \r\n)替换为\n
  • 在Python 3,可以通过open函数的newline参数来控制换行模式(Universal new line mode)
  • 读取时候,不指定newline,则默认开启Universal new line mode,所有\n, \r, or \r\n被默认转换为\n
  • 写入时,不指定newline,则换行符为各系统默认的换行符(\n, \r, or \r\n, ),指定为newline=’\n’,则都替换为\n(相当于Universal new line mode)
  • 不论读或者写时,newline=""都表示不转换,即关闭Universal new line mode

返回值
_io.TextIOWrapper类型对象,即被创建的文件对象,为一个迭代器

函数形参

  • file

    • 传入一个字符串类型对象,用于指定要打开或者创建的文件的名称
  • mode

    • 传入一个字符串类型对象,用于指定模式,x、r、w、a只能选择一个使用

    • r: 读模式(默认),如果文件不存在,则抛出异常;如果文件已存在,则文件指针位于文件开头。只能进行读操作

      • "rb"对于任何类型的文件(文本文件和二进制文件)都可以进行读取,因为无论是文本文件还是二进制文件,在存储时都是以二进制形式进行存储,本质都是二进制数据
      • "r"需要对要读取的文件内容(二进制数据)进行翻译(编码),但是像图片、视频、压缩包这样的二进制文件是不能够被编码的
      • 打开文件的时候,建议使用"rb"而不要使用"r"
    • w:写模式,如果文件不存在,则创建文件;如果文件已存在,则打开时自动清空原有内容。只能进行写操作

    • x:只写模式,如果文件不存在,则创建文件;如果文件已存在,则抛出异常。只能进行写操作

    • a:追加模式,如果文件不存在,则创建文件;如果文件已存在,则不会清空内容,而是文件指针自动移动到文件末尾

    • +:读写模式(不可单独使用),其他模式与该模式联合使用时(比如w+,r+,a+),针对文件对象可以同时允许进行读和写的操作

      • 在 r+ 模式下使用 write()函数,并不会先清空文件内容,新内容会添加在文件的开头部分,而且会覆盖开头部分原来已有的内容,其他内容保留
      • 在 w+ 模式下使用 write()函数,原有的文件的内容将会先完全被清空,新内容会添加在文件的开头部分
      • 在 a+ 模式下使用 write()函数,并不会先清空文件内容,新内容会添加在文件的末尾部分
    • b:二进制模式(不可单独使用),打开文件时不允许指定encoding形参

    • t:文本模式(默认,不可单独使用),打开文件时可以指定encoding形参

      • 如果以文本模式打开文件并进行读取(即r),那么会自动对读取的二进制数据进行编码操作,这对于打开一些二进制文件来说是没有必要的,而且可能会出现编码错误的问题。类似随意拿一段二进制数据进行编码,二进制数据的长度可能都对不上,不一定能成功编码
      • 如果以文本模式打开文件并进行写入(即w),那么会自动对写入的文本数据进行解码操作
    • U:通用换行模式(Universal new line mode)。该模式会把所有的换行符(\r \n \r\n)替换为\n

      • U模式不能和 ‘x’, ‘w’, ‘a’, 或者 ‘+’ 中的任意一个结合使用,即只能和 ‘r’ 结合使用
      • 在Python 2中如果需要读取文本文件的内容,那么请用rU来读取(强烈推荐)
      • 在Python 3中可以通过open函数的newline参数来控制Universal new line mode,Python 3不推荐用rU模式,如果使用,那么会有警告 DeprecationWarning: ‘U’ mode is deprecated
  • buffering

    • 传入一个int类型对象,用于指定读写文件的缓存模式。
      • 0:表示不缓存
      • 1:表示行缓存(仅限文本模式)
      • 大于1:表示缓冲区的大小
  • encoding

    • 传入一个字符串类型对象,用于指定对文本进行编码和解码的方式,只适用于文本模式,可以使用Python支持的任何格式,如GBK、utf8、CP936等等

产生异常的情况
由于指定文件不存在、访问权限不够、磁盘空间不够或者其他原因导致创建文件失败,则会抛出异常

案例

fp =  open('test.txt', 'w+')    #以读写模式打开,test.txt文件,打开时会自动清空内容
print(fp)       #打印文件对象,会得到文件名称、open()函数的访问模式及编码方式

#输出结果:<_io.TextIOWrapper name='test.txt' mode='w+' encoding='cp936'>

回车符\r与换行符\n

打字机的回车和换行

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行,需要进行回车换行这两个操作,此过程,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失
在这里插入图片描述
于是,研究人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车\r”,告诉打字机把打印头定位在左边界;另一个叫做“换行\n”,告诉打字机把纸向下移一行

打字机的回车换行与计算机的回车换行

首先,打字机打完一行,意味着,从左到右把一行的字打完了,此时打印机的打印头处于最右边,所以,想要继续打字,需要

  • 先将把打印头移动到最左边,这个动作被称为回车
  • 然后打字所用的纸张,换到下一行,这个动作被称为换行(其是通过打字机的滚筒滚动,将纸向前滚动,就相当于打印头换了一行,处在新一行的最左边的开始的位置了,即所谓的走纸)

把打字机的回车换行对应到计算机系统中

  • 回车:将当前光标移动到同一行中的最左边(假设是从左到右的输入方式)
  • 换行:保持当前光标的水平位置位置不变,换到下一行
  • 将回车和换行联合起来,才是我们所常理解的含义:输入完一行后,回车换行到下一行,即光标不仅仅是回到了最左边,而且也换到了下一行

各种系统中表示回车换行含义所用字符

系统类型回车换行所用字符
Linux/Unix\n = Newline = 0x0A = 10 = LF =Line Feed = 换行
Mac\r = Return = 0x0D = 13 = CR = Carriage Return = 回车
Windows/Dos\r \n = 0x0D 0x0A = CR LF = 回车 换行

有些操作系统中只用一个换行符\n的原因

  • 那时,计算机的存储器还很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以
回车换行在不同系统中所用不同字符所导致的问题
  • Linux中打开Windows中编写的文件
    • 由于Linux用\n=0x0A=10=LF来表示换行,所以,打开Windows中编写的文件的时候,如果其中有换行,即其中有\r \n= 0x0D 0x0A,此时,就会被处理为:只将\n字符理解为换行,而把\r字符看作为一个单独的字符进行显示
    • 同时由于\r字符对应ASCII的值是0x0D=13,是个控制字符,对应的是用Ctrl+M=一个Ctrl加上M字符输入得到的,所以最终在Linux的文本编辑器中,如Vi中,\r字符显示为一个"^M"
  • Mac中打开Windows中编写的文件
    • 由于Mac用\r=0x0D=13=CR来表示换行,所以,打开Windows中编写的文件的时候,如果其中有换行,即其中有\r \n= 0x0D 0x0A,此时,就会被处理为:只将\r字符理解为换行,而把\n字符看作为一个单独的字符进行显示
    • 同时由于\n字符对应ASCII的值是0x0A=10,是个控制字符,对应的是用Ctrl+J=一个Ctrl加上J字符输入得到的,所以最终在Mac的文本编辑器中,\n字符显示为一个"^J"
  • Windows中打开Mac或Linux中的文件
    • 经过测试,发现,在windows中,估计把原先的0x0D 0x0A去掉其中任何一个的话,然后用windows下的文本类编辑器再去打开,除了最简单的Windows自带的记事本,无法正常识别回车换行,内容被显示到同一行中之外;其他一些编辑器,如Notepad2,Notepad++,Windows自带的记事本WordPad,都可以正常识别成回车换行,不会将内容都输出在同一行

文件对象

文件对象是一个迭代器,可以直接使用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
"""


with open('test.txt', 'r', newline='') as fp:   # newline=''表示关闭通用换行模式(Universal new line mode)
    print(repr(fp.read()))  # 在Windows系统中,换行符为'\r\n',而在Linux/Unix系统中,换行符为'\n'
    print('此时的偏移量为:', fp.tell())

#输出结果:
"""
'12345678910\r\n01987654321'
此时的偏移量为: 24
"""


#以文本模式打开图片,那么会自动对读取的数据进行编码操作,此时可能会出现编码错误的问题
#因为图片为一个二进制文件,不一定能成功编码
with open(R"dog.jpg", "r") as f:
    file_data = f.read()	

#引发异常:UnicodeDecodeError: 'gbk' codec can't decode byte 0xff in position 0: illegal multibyte sequence

4.readline()

作用
针对文件指针所在行的内容(可能是部分)进行读取

可以传入参数,来指定所读的最大字符数(针对于文本文件)或者 字节数(针对二进制文件)
如果传入的参数大于实际的该行的最大可读取数,则会在行尾自动结束读取
如果不传入参数,默认表示读取该行的所有内容

在进行读取的过程中,文件指针也会进行相应的移动

返回值
字符串类型对象 读取到的内容

案例

# 内置函数repr用于进行二次转化,可以用于显示文件内容中的不可见字符,比如\n
# 比如\n原来表示换行,为一个字符,二次转义以后的\\\n表示两个字符\\和n
with open('test.txt', 'r', newline='') as fp:
    print(repr("第一行内容:<%s>" % fp.readline()))  
    print(repr("第二行内容:<%s>" % fp.readline()))
    print(repr("第三行内容:<%s>" % fp.readline()))

#输出结果:
"""
'第一行内容:<12345678910\r\n>'
'第二行内容:<01987654321>'
'第三行内容:<>'
"""


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)		# 因为每一行有11个可见字符 12345678910,2两个不可见字符 \r\n
    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']

with open('test.txt', 'r') as fp:
    lines = fp.readlines(100)
    lines_content = list(map(str.strip, lines))     # 通常我们使用strip方法去除末尾的换行符
    print(lines_content)

#输出结果:['12345678910', '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
"""

with open("1.txt", "r", encoding="utf-8") as f:
    file_data = f.read()
    print("文件数据:", file_data)

with open("123.txt", "w", encoding="gbk") as f:
    f.write(file_data)

#输出结果:
"""
文件数据: Hello_world!(你好世界!)
"""
#文件1.txt的内容:Hello_world!(你好世界!)
#文件123.txt的内容:Hello_world!(ÄãºÃÊÀ½ç!)

with open("1.txt", "rb") as f:
    file_data = f.read()
    print("文件数据:", file_data)

with open("123.txt", "wb") as f:
    f.write(file_data)

#输出结果:
"""
文件数据: b'Hello_world!(\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c!)\r\n\r\n\r\n'
"""
#文件1.txt的内容:Hello_world!(你好世界!)
#文件123.txt的内容:Hello_world!(你好世界!)

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)“称为"下文”
再比如MySQL中,我们use不同的数据库后,再输入show tables;来查询当前数据库的所有数据表,同样的命令输入,但是所处的数据库不同(即上下文 / 环境不同),导致终端打印出来的信息不同

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'中空空如也

可以通过异常处理机制来捕获异常,使得资源正常关闭

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    
    print("enter方法被调用")
    f = open(path, mode)

    try:
        yield f

    except Exception as e:
        print("发生异常:", e)

    finally:
        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
exit方法被调用
"""
#文件内容:123
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值