python with as用法详解
任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。但资源都是有限的,在写程序时,必须保证这些资源在使用过后得到释放,不然就容易造成资源泄露,轻者使得系统处理缓慢,严重时会使系统崩溃。例如,前面在介绍文件操作时,一直强调打开的文件最后一定要关闭,否则会程序的运行造成意想不到的隐患。但是,即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出
任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。但资源都是有限的,在写程序时,必须保证这些资源在使用过后得到释放,不然就容易造成资源泄露,轻者使得系统处理缓慢,严重时会使系统崩溃。
例如,前面在介绍文件操作时,一直强调打开的文件最后一定要关闭,否则会程序的运行造成意想不到的隐患。但是,即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出了异常,还是无法及时关闭文件。
为了更好地避免此类问题,不同的编程语言都引入了不同的机制。在 python 中,对应的解决方式是使用 with as 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资源。
简单的理解,同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。常见构建上下文管理器的方式有 2 种,分别是基于类实现和基于生成器实现。
with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。
所以使用with处理的对象必须有__enter__()和__exit__()这两个方法。
其中__enter__()方法在语句体(with语句包裹起来的代码块)执行之前进入运行,__exit__()方法在语句体执行完毕退出后运行。
简单的理解,同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。也就是说,上下文管理器必须实现如下两个方法:
- __enter__(self):进入上下文管理器自动调用的方法。该方法会在 with as 代码块执行之前执行。如果 with 语句有 as子句,那么该方法的返回值会被赋值给 as 子句后的变量;该方法可以返回多个值,因此在 as 子句后面也可以指定多个变量(多个变量必须由“()”括起来组成元组)。
- __exit__(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。该方法会在 with as 代码块执行之后执行。如果 with as 代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都为 None:如果 with as 代码块因为异常而中止,打印异常栈之前程序也自动调用该方法,使用 sys.exc_info 得到的异常信息将作为调用该方法的参数。
当 with as 操作上下文管理器时,就会在执行语句体之前,先执行上下文管理器的 __enter__() 方法,然后再执行语句体,最后执行 __exit__() 方法。
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
With语句的基本语法格式:
with expression [as target]:
代码块
参数说明:
expression:是一个需要执行的表达式;
target:是一个变量或者元组,存储的是expression表达式执行返回的结果,可选参数。
例如:
文本内容如下:
我无法大是大非
我无法大是大非
我无法大是大非
with语句的工作原理:
紧跟with后面的语句会被求值,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as关键字后面的变量,当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。
with语句最关键的地方在于被求值对象必须有__enter__()和__exit__()这两个方法,那我们就可以通过自己实现这两方法来自定义with语句处理异常。
基于类实现上下文管理器:
#encoding=utf-8
class opened(object):
def __init__(self,filename):
self.handle=open(filename)
print "Resource:%s"%filename
def __enter__(self):
print "[enter%s]: Allocate resource."%self.handle
return self.handle#可以返回不同的对象
def __exit__(self,exc_type,exc_value,exc_trackback):
print "[Exit %s]: Free resource." %self.handle
if exc_trackback is None:
print "[Exit %s]:Exited without exception."%self.handle
self.handle.close()
else:
print "[Exit %s]: Exited with exception raised."%self.handle
return False # 可以省略,缺省的None也是被看做是False
with opened(r'd:\\xxx.txt') as fp:
for line in fp.readlines():
print line
结果:
opened中的__enter__() 返回的是自身的引用,这个引用可以赋值给 as 子句中的fp变量;
返回值的类型可以根据实际需要设置为不同的类型,不必是上下文管理器对象本身。
__exit__() 方法中对变量exc_trackback进行检测,如果不为 None,表示发生了异常,返回 False 表示需要由外部代码逻辑对异常进行处理;
如果没有发生异常,缺省的返回值为 None,在布尔环境中也是被看做 False,但是由于没有异常发生,__exit__() 的三个参数都为 None,上下文管理代码可以检测这种情况,做正常处理。__exit__()方法的3个参数,分别代表异常的类型、值、以及堆栈信息。
基于生成器的上下文管理器
除了基于类的上下文管理器,它还可以基于生成器实现。接下来先看一个例子。比如,我们可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with as 语句:
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
try:
print(f'111')
f = open(name, mode)
yield f
finally:
print(f'222')
f.close()
with file_manager('a.txt', 'w') as f:
print(f'333')
f.write('hello world')
运行结果如下:
这段代码中,函数 file_manager() 就是一个生成器,当我们执行 with as 语句时,便会打开文件,并返回文件对象 f;当 with 语句执行完后,finally 中的关闭文件操作便会执行。另外可以看到,使用基于生成器的上下文管理器时,不再用定义 __enter__() 和 __exit__() 方法,但需要加上装饰器 @contextmanager,这一点新手很容易疏忽。
需要强调的是,基于类的上下文管理器和基于生成器的上下文管理器,这两者在功能上是一致的。只不过,基于类的上下文管理器更加灵活,适用于大型的系统开发,而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。
无论使用哪一种,不能忘记在方法“__exit__()”或者是 finally 块中释放资源,这一点尤其重要。with as语句的最大优点就是可以自动回收资源处理。
python读写文件常用函数
1.调用 os 模块中的 remove 函数,可以将文件删除
- import os
- os.remove("a.txt")
2.打开文件
open() 函数用于创建或打开指定文件,该函数的常用语法格式如下:
file = open(file_name [, mode='r' [ , buffering=-1 [ , encoding = None ]]])
此格式中,用 [] 括起来的部分为可选参数,即可以使用也可以省略。其中,各个参数所代表的含义如下:
- file:表示要创建的文件对象。
- file_name:要创建或打开文件的文件名称,该名称要用引号(单引号或双引号都可以)括起来。需要注意的是,如果要打开的文件和当前执行的代码文件位于同一目录,则直接写文件名即可;否则,此参数需要指定打开文件所在的完整路径。
- mode:可选参数,用于指定文件的打开模式。可选的打开模式如表 1 所示。如果不写,则默认以只读(r)模式打开文件。
- buffering:可选参数,用于指定对文件做读写操作时,是否使用缓冲区(本节后续会详细介绍)。
- encoding:手动设定打开文件时所使用的编码格式,不同平台的 ecoding 参数值也不同,以 Windows 为例,其默认为 cp936(实际上就是 GBK 编码)。
open() 函数支持的文件打开模式如表 1 所示。
模式 | 意义 | 注意事项 |
---|---|---|
r | 只读模式打开文件,读文件内容的指针会放在文件的开头。 | 操作的文件必须存在。 |
rb | 以二进制格式、采用只读模式打开文件,读文件内容的指针位于文件的开头,一般用于非文本文件,如图片文件、音频文件等。 | |
r+ | 打开文件后,既可以从头读取文件内容,也可以从开头向文件中写入新的内容,写入的新内容会覆盖文件中等长度的原有内容。 | |
rb+ | 以二进制格式、采用读写模式打开文件,读写文件的指针会放在文件的开头,通常针对非文本文件(如音频文件)。 | |
w | 以只写模式打开文件,若该文件存在,打开时会清空文件中原有的内容。 | 若文件存在,会清空其原有内容(覆盖文件);反之,则创建新文件。 |
wb | 以二进制格式、只写模式打开文件,一般用于非文本文件(如音频文件) | |
w+ | 打开文件后,会对原有内容进行清空,并对该文件有读写权限。 | |
wb+ | 以二进制格式、读写模式打开文件,一般用于非文本文件 | |
a | 以追加模式打开一个文件,对文件只有写入权限,如果文件已经存在,文件指针将放在文件的末尾(即新写入内容会位于已有内容之后);反之,则会创建新文件。 | |
ab | 以二进制格式打开文件,并采用追加模式,对文件只有写权限。如果该文件已存在,文件指针位于文件末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
a+ | 以读写模式打开文件;如果文件存在,文件指针放在文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
ab+ | 以二进制模式打开文件,并采用追加模式,对文件具有读写权限,如果文件存在,则文件指针位于文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 |
文件打开模式,直接决定了后续可以对文件做哪些操作。例如,使用 r 模式打开的文件,后续编写的代码只能读取文件,而无法修改文件内容。
图 2 中,将以上几个容易混淆的文件打开模式的功能做了很好的对比:
open()文件对象常用的属性
成功打开文件之后,可以调用文件对象本身拥有的属性获取当前文件的部分信息,其常见的属性为:
- file.name:返回文件的名称;
- file.mode:返回打开文件时,采用的文件打开模式;
- file.encoding:返回打开文件时使用的编码格式;
- file.closed:判断文件是否己经关闭。
举个例子:
# 以默认方式打开文件
f = open('my_file.txt')
# 输出文件是否已经关闭
print(f.closed)
# 输出访问模式
print(f.mode)
#输出编码格式
print(f.encoding)
# 输出文件名
print(f.name)
程序执行结果为:
False
r
cp936
my_file.txt
3.读取文件
read() 函数的基本语法格式如下:
file.read([size])
其中,file 表示已打开的文件对象;size 作为一个可选参数,用于指定一次最多可读取的字符(字节)个数,如果省略,则默认一次性读取所有内容。
readline() 函数用于读取文件中的一行,包含最后的换行符“\n”。此函数的基本语法格式为:
file.readline([size])
其中,file 为打开的文件对象;size 为可选参数,用于指定读取每一行时,一次最多读取的字符(字节)数。
和 read() 函数一样,此函数成功读取文件数据的前提是,使用 open() 函数指定打开文件的模式必须为可读模式(包括 r、rb、r+、rb+ 4 种)。
readlines() 函数用于读取文件中的所有行,它和调用不指定 size 参数的 read() 函数类似,只不过该函数返回是一个字符串列表,其中每个元素为文件中的一行内容。
和 readline() 函数一样,readlines() 函数在读取每一行时,会连同行尾的换行符一块读取。
readlines() 函数的基本语法格式如下:
file.readlines()
其中,file 为打开的文件对象。和 read()、readline() 函数一样,它要求打开文件的模式必须为可读模式(包括 r、rb、r+、rb+ 4 种)。
4.向文件中写入指定内容
file.write(string)
其中,file 表示已经打开的文件对象;string 表示要写入文件的字符串(或字节串,仅适用写入二进制文件中)。
注意,在使用 write() 向文件中写入数据,需保证使用 open() 函数是以 r+、w、w+、a 或 a+ 的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。
writelines() 函数,可以实现将字符串列表写入文件中
以 a.txt 文件为例,通过使用 writelines() 函数,可以轻松实现将 a.txt 文件中的数据复制到其它文件中,实现代码如下:
f = open('a.txt', 'r')
n = open('b.txt','w+')
n.writelines(f.readlines())
n.close()
f.close()
执行此代码,在 a.txt 文件同级目录下会生成一个 b.txt 文件,且该文件中包含的数据和 a.txt 完全一样。
需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符。上面例子中,之所以 b.txt 文件中会逐行显示数据,是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。
5.关闭文件
file.close()
其中,file 表示已打开的文件对象。
6.文件指针操作字符
文件中每个数据,以 b 模式打开,每个数据就是一个字节;以普通模式打开,每个数据就是一个字符
读取文件指针位置
file.tell()
其中,file 表示文件对象
seek() 函数用于将文件指针移动至指定位置,该函数的语法格式如下:
file.seek(offset[, whence])
其中,各个参数的含义如下:
- file:表示文件对象;
- whence:作为可选参数,用于指定文件指针要放置的位置,该参数的参数值有 3 个选择:0 代表文件头(默认值)、1 代表当前位置、2 代表文件尾。
- offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。例如,当
whence == 0 &&offset == 3
(即 seek(3,0) ),表示文件指针移动至距离文件开头处 3 个字符的位置;当whence == 1 &&offset == 5
(即 seek(5,1) ),表示文件指针向后移动,移动至距离当前位置 5 个字符处。
注意,当 offset 值非 0 时,python要求文件必须要以二进制格式打开,否则会抛出 io.UnsupportedOperation 错误。

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)