浅谈 Python 的 with 语句:https://developer.ibm.com/zh/articles/os-cn-pythonwith/

python3,浅谈with的神奇魔法:https://blog.csdn.net/lxy210781/article/details/81176687

Python 的 with 语句详解:https://www.jb51.net/article/51045.htm

深入理解 Python 的 With-as 语句:https://cloud.tencent.com/developer/article/1083148

python with statement 进阶理解:https://www.iteye.com/blog/jianpx-505469

Python 中的with关键字使用详解:https://www.jb51.net/article/92387.htm

由来

with…as 是 python 的控制流语句,像 if ,while一样。with…as 语句是简化版的 try except finally语句。

先理解一下 try…except…finally 语句是干啥的。实际上 try…except 语句和 try…finally 语句是两种语句,用于不同的场景。但是当二者结合在一起时,可以“实现稳定性和灵活性更好的设计”。

1. try…except 语句

用于处理程序执行过程中的异常情况,比如语法错误、从未定义变量上取值等等,也就是一些python程序本身引发的异常、报错。比如你在python下面输入 1 / 0:

>>> 1/0
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

系统会给你一个 ZeroDivisionError 的报错。说白了就是为了防止一些报错影响你的程序继续运行,就用try语句把它们抓出来(捕获)。

try…except 的标准格式:

try:  ## normal block
except A:  ## exc A block
except:  ## exc other block
else:  ## noError block  

程序执行流程是:

–>执行normal block
–>发现有A错误,执行 exc A block(即处理异常)
–>结束如果没有A错误呢?
–>执行normal block
–>发现B错误,开始寻找匹配B的异常处理方法,发现A,跳过,发现except others(即except:),执行exc other block
–>结束如果没有错误呢?
–>执行normal block
–>全程没有错误,跳入else 执行noError block
–>结束

Tips: 我们发现,一旦跳入了某条except语句,就会执行相应的异常处理方法(block),执行完毕就会结束。不会再返回try的normal block继续执行了。

try:a = 1 / 2 #a normal number/variableprint(a)b = 1 / 0 # an abnormal number/variableprint(b)c = 2 / 1 # a normal number/variableprint(c)
except:print("Error")

结果是,先打出了一个0,又打出了一个Error。就是把ZeroDivisionError错误捕获了。

先执行 try 后面这一堆语句,由上至下:

  • step1: a 正常,打印a. 于是打印出0.5 (python3.x以后都输出浮点数)
  • step2: b, 不正常了,0 不能做除数,所以这是一个错误。直接跳到except报错去。于是打印了Error。
  • step3: 其实没有step3,因为程序结束了。c是在错误发生之后的b语句后才出现,根本轮不到执行它。也就看不到打印出的c了

但这还不是 try/except 的所有用法

except后面还能跟表达式的!

所谓的表达式,就是错误的定义。也就是说,我们可以捕捉一些我们想要捕捉的异常。而不是什么异常都报出来。

异常分为两类:

  • python标准异常
  • 自定义异常

我们先抛开自定义异常(因为涉及到类的概念),看看 except 都能捕捉到哪些 python 标准异常。请查看菜鸟笔记

https://www.runoob.com/python/python-exceptions.html

2. try…finallly 语句

用于无论执行过程中有没有异常,都要执行清场工作。

try:  execution block  ##正常执行模块
except A:  exc A block ##发生A错误时执行
except B:  exc B block ##发生B错误时执行
except:  other block ##发生除了A,B错误以外的其他错误时执行
else:  if no exception, jump to here ##没有错误时执行
finally:  final block  ##总是执行  

tips: 注意顺序不能乱,否则会有语法错误。如果用 else 就必须有 except,否则会有语法错误。

try:a = 1 / 2print(a)print(m)  # 抛出 NameError异常, 此后的语句都不在执行b = 1 / 0print(b)c = 2 / 1print(c)
except NameError:print("Ops!!")  # 捕获到异常
except ZeroDivisionError:print("Wrong math!!")
except:print("Error")
else:print("No error! yeah!")
finally:  # 是否异常都执行该代码块print("Successfully!")

1. with 语句的原理

  • 上下文管理协议(Context Management Protocol):包含方法 __enter__()__exit__(),支持该协议的对象要实现这两个方法。
  • 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 __enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

说完上面两个概念,我们再从 with 语句的常用表达式入手,一段基本的 with 表达式,其结构是这样的:

with context_expression [as target(s)]:...with-body...

其中 context_expression 可以是任意表达式;as target(s) 是可选的。

with 语句执行过程 。在语义上等价于:

    context_manager = context_expressionexit = type(context_manager).__exit__value = type(context_manager).__enter__(context_manager)exc = True   # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理try:try:target = value  # 如果使用了 as 子句with-body     # 执行 with-bodyexcept:# 执行过程中有异常发生exc = False# 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常# 由外层代码对异常进行处理if not exit(context_manager, *sys.exc_info()):raisefinally:# 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出# 或者忽略异常退出if exc:exit(context_manager, None, None, None)# 缺省返回 None,None 在布尔上下文中看做是 False

可以看到,with 和 try finally 有下面的等价流程:

try:  执行 __enter__的内容  执行 with_block.
finally:  执行 __exit__内容  
  1. 执行 context_expression ,生成上下文管理器 context_manager
  2. 调用上下文管理器的 __enter__() 方法;如果使用了 as 子句,则将 __enter__() 方法的 返回值 赋值给 as 子句中的 target(s)
  3. 执行语句体 with-body
  4. 不管执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法,__exit__() 方法负责执行 "清理" 工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 __exit__(None, None, None) ;如果执行过程中出现异常,则使用 sys.excinfo 得到的异常信息为参数调用  __exit__(exc_type, exc_value, exc_traceback)
  5. 出现异常时,如果 __exit__(type, value, traceback) 返回 False,则会重新抛出异常,让 with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理

那么__enter____exit__是怎么用的方法呢?我们直接来看一个栗子好了。

程序无错的例子:

class Sample(object):             # object类是所有类最终都会继承的类def __enter__(self):          # 类中函数第一个参数始终是self,表示创建的实例本身print("In __enter__()")return "Foo"def __exit__(self, type, value, trace):print("In __exit__()")def get_sample():return Sample()with get_sample() as sample:print("sample:", sample)print(Sample)    # 这个表示类本身   <class '__main__.Sample'>
print(Sample())  # 这表示创建了一个匿名实例对象 <__main__.Sample object at 0x00000259369CF550>'''
In __enter__()
sample: Foo
In __exit__()
<class '__main__.Sample'>
<__main__.Sample object at 0x00000226EC5AF550>'''

步骤分析:
–> 调用get_sample()函数,返回Sample类的实例;
–> 执行Sample类中的__enter__()方法,打印"In__enter_()"字符串,并将字符串“Foo”赋值给as后面的sample变量;
–> 执行with-block码块,即打印"sample: %s"字符串,结果为"sample: Foo"
–> 执行with-block码块结束,返回Sample类,执行类方法__exit__()。因为在执行with-block码块时并没有错误返回,所以type,value,trace这三个arguments都没有值。直接打印"In__exit__()"

程序有错的例子:

class Sample:def __enter__(self):return selfdef __exit__(self, type, value, trace):print("type:", type)print("value:", value)print("trace:", trace)def do_something(self):bar = 1 / 0return bar + 10with Sample() as sample:sample.do_something()'''
type: <class 'ZeroDivisionError'>
value: division by zero
trace: <traceback object at 0x0000019B73153848>
Traceback (most recent call last):File "F:/机器学习/生物信息学/Code/first/hir.py", line 16, in <module>sample.do_something()File "F:/机器学习/生物信息学/Code/first/hir.py", line 11, in do_somethingbar = 1 / 0
ZeroDivisionError: division by zero
'''

步骤分析:
–> 实例化Sample类,执行类方法__enter__(),返回值self也就是实例自己赋值给sample。即sampleSample的一个实例(对象);
–>执行with-block码块: 实例sample调用方法do_something();
–>执行do_something()第一行 bar = 1 / 0,发现ZeroDivisionError,直接结束with-block代码块运行
–>执行类方法__exit__(),带入ZeroDivisionError的错误信息值,也就是type,valuetrace,并打印它们。

如果有多个项目,则会视作存在多个 with 语句嵌套来处理多个上下文管理器: ( https://docs.python.org/zh-cn/3/reference/compound_stmts.html#the-with-statement )

with A() as a, B() as b:SUITE在语义上等价于:with A() as a:with B() as b:SUITE

在 3.1 版更改: 支持多个上下文表达式。

参见:PEP 343 - "with" 语句。Python with 语句的规范描述、背景和示例。

2. 自定义上下文管理器

开发人员可以自定义支持上下文管理协议的类。自定义的上下文管理器要实现上下文管理协议所需要的 enter() 和 exit() 两个方法:

  • contextmanager.__enter__() :进入上下文管理器的运行时上下文,在语句体执行前调用。with 语句将该方法的返回值赋值给 as 子句中的 target,如果指定了 as 子句的话
  • contextmanager.__exit__(exc_type, exc_value, exc_traceback) :退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为None。如果发生异常时,返回True 表示不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。如果该方法内部产生异常,则会取代由 statement-body 中语句产生的异常。要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False 就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常

下面通过一个简单的示例来演示如何构建自定义的上下文管理器。

注意,上下文管理器必须同时提供 __enter__() 和 __exit__() 方法的定义,缺少任何一个都会导致 AttributeError;with 语句会先检查是否提供了 __exit__() 方法,然后检查是否定义了 __enter__() 方法。

# coding = utf-8class DBManager(object):def __init__(self):passdef __enter__(self):print('__enter__')return selfdef __exit__(self, exc_type, exc_val, exc_tb):print('__exit__')return Truedef getInstance():return DBManager()with getInstance() as dbManagerIns:print('with demo')'''
运行结果:
__enter__
with demo
__exit__
'''

with 后面必须跟一个上下文管理器,如果使用了 as,则是把上下文管理器的 __enter__() 方法的返回值赋值给 target,target 可以是单个变量,或者由 "()" 括起来的元组(不能是仅仅由 "," 分隔的变量列表,必须加 "()")

结果分析:当我们使用 with 的时候,__enter__方法被调用,并且将返回值赋值给 as 后面的变量,并且在退出 with 的时候自动执行 __exit__ 方法

class With_work(object):def __enter__(self):"""进入with语句的时候被调用"""print('enter called')return "xxt"def __exit__(self, exc_type, exc_val, exc_tb):"""离开with的时候被with调用"""print('exit called')with With_work() as as_f:print(f'as_f : {as_f}')print('hello with')'''
enter called
as_f : xxt
hello with
exit called
'''

示例 2:

自定义支持 with 语句的对象

class DummyResource:def __init__(self, tag):self.tag = tagprint(f'Resource [{tag}]')def __enter__(self):print(f'[Enter {self.tag}]: Allocate resource.')return self  # 可以返回不同的对象def __exit__(self, exc_type, exc_value, exc_tb):""":param exc_type: 错误的类型:param exc_value: 错误类型对应的值 :param exc_tb: 代码中错误发生的位置 :return:"""print(f'[Exit {self.tag}]: Free resource.')if exc_tb is None:print(f'[Exit {self.tag}]: Exited without exception.')else:print(f'[Exit {self.tag}]: Exited with exception raised.')return False  # 可以省略,缺省的None也是被看做是False# 第一个 with 语句
num = 50
print('*' * num)
with DummyResource('First'):print('[with-body] Run without exceptions.')
print('*' * num)# 第二个 with 语句
print('*' * num)
with DummyResource('second'):print('[with-body] Run with exception.')raise Exceptionprint('[with-body] Run with exception. Failed to finish statement-body!')
print('*' * num)# 嵌套 with 语句
print('*' * num)
with DummyResource('Normal'):print('[with-body] Run without exceptions.')with DummyResource('With-Exception'):print('[with-body] Run with exception.')raise Exceptionprint('[with-body] Run with exception. Failed to finish statement-body!')
print('*' * num)

DummyResource 中的 __enter__() 返回的是自身的引用,这个引用可以赋值给 as 子句中的 target 变量;返回值的类型可以根据实际需要设置为不同的类型,不必是上下文管理器对象本身。

__exit__() 方法中对变量 exctb 进行检测,如果不为 None,表示发生了异常,返回 False 表示需要由外部代码逻辑对异常进行处理;注意到如果没有发生异常,缺省的返回值为 None,在布尔环境中也是被看做 False,但是由于没有异常发生,__exit__() 的三个参数都为 None,上下文管理代码可以检测这种情况,做正常处理。

执行结果:

**************************************************
Resource [First]
[Enter First]: Allocate resource.
[with-body] Run without exceptions.
[Exit First]: Free resource.
[Exit First]: Exited without exception.
**************************************************
**************************************************
Resource [second]
[Enter second]: Allocate resource.
[with-body] Run with exception.
[Exit second]: Free resource.
[Exit second]: Exited with exception raised.
Traceback (most recent call last):File "temp.py", line 30, in <module>raise Exception
Exception

第1个 with  语句执行结果:可以看到,正常执行时会先执行完语句体 with-body,然后执行 __exit__() 方法释放资源。

第2个 with 语句的执行结果:可以看到,with-body 中发生异常时with-body 并没有执行完,但资源会保证被释放掉,同时产生的异常由 with 语句之外的代码逻辑来捕获处理。

因为第2个with语句发生异常,所以 嵌套 with 语句没有执行。。。

3. 自动关闭文件

我们都知道打开文件有两种方法:

  • f = open()
  • with open() as f:

这两种方法的区别就是第一种方法需要我们自己关闭文件;f.close(),而第二种方法不需要我们自己关闭文件,无论是否出现异常,with都会自动帮助我们关闭文件,这是为什么呢?

我们先自定义一个类,用with来打开它:

class Foo(object):def __enter__(self):print("enter called")def __exit__(self, exc_type, exc_val, exc_tb):print("exit called")print("exc_type :%s" % exc_type)print("exc_val :%s" % exc_val)print("exc_tb :%s" % exc_tb)with Foo() as foo:print("hello python")a = 1 / 0print("hello end")'''
enter called
Traceback (most recent call last):
hello python
exit called
exc_type :<class 'ZeroDivisionError'>
exc_val :division by zeroFile "F:/workspaces/python_workspaces/flask_study/with.py", line 25, in <module>a = 1/0
exc_tb :<traceback object at 0x0000023C4EDBB9C8>
ZeroDivisionError: division by zeroProcess finished with exit code 1
'''

执行结果的输入顺序,分析如下:

当我们 with Foo() as foo: 时,此时会执行 __enter__方法,然后进入执行体,也就是:

print("hello python")
a = 1/0
print("hello end")

语句,但是在 a=1/0 出现了异常,with将会中止,此时就执行__exit__方法,就算不出现异常,当执行体被执行完毕之后,__exit__方法仍然被执行一次。

我们回到 with open("file")as f: 不用关闭文件的原因就是在 __exit__ 方法中,存在关闭文件的操作,所以不用我们手工关闭文件,with已将为我们做好了这个操作,这就可以理解了。

4. contextlib 模块

contextlib --- 为 with语句上下文提供的工具:https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.asynccontextmanager

contextlib 模块提供了3个对象,使用这些对象,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。

  • 装饰器 contextmanager
  • 函数 nested
  • 上下文管理器 closing

装饰器 contextmanager

contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 enter() 和 exit() 方法由 contextmanager 负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常 RuntimeError;产生的值会赋值给 as 子句中的 target,如果使用了 as 子句的话。下面看一个简单的例子。

from contextlib import contextmanager@contextmanager
def demo():print('[Allocate resources]')print('Code before yield-statement executes in __enter__')yield '*** contextmanager demo ***'print('Code after yield-statement executes in __exit__')print('[Free resources]')with demo() as value:print(f'Assigned Value: {value}')'''
[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]
'''

可以看到,生成器函数中 yield 之前的语句在 enter() 方法中执行,yield 之后的语句在 exit() 中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。

需要注意的是,contextmanager 只是省略了 enter() / exit() 的编写,但并不负责实现资源的”获取”和”清理”工作;”获取”操作需要定义在 yield 语句之前,”清理”操作需要定义 yield 语句之后,这样 with 语句在执行 enter() / exit() 方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。

函数 nested

nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。

nested 语法with nested(A(), B(), C()) as (X, Y, Z):# with-body code here类似于:with A() as X:with B() as Y:with C() as Z:# with-body code here需要注意的是,发生异常后,如果某个上下文管理器的 exit() 方法对异常处理返回 False,
则更外层的上下文管理器不会监测到异常。

上下文管理器 closing

closing 的实现如下:

class closing(object):# help doc heredef __init__(self, thing):self.thing = thingdef __enter__(self):return self.thingdef __exit__(self, *exc_info):self.thing.close()

上下文管理器会将包装的对象赋值给 as 子句的 target 变量,同时保证打开的对象在 with-body 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报 AttributeError 错误。

自定义支持 closing 的对象

from contextlib import closingclass ClosingDemo(object):def __init__(self):self.acquire()def acquire(self):print('Acquire resources.')def free(self):print('Clean up any resources acquired.')def close(self):self.free()with closing(ClosingDemo()):print('Using resources')'''
Acquire resources.
Using resources
Clean up any resources acquired.
'''

closing 适用于提供了 close() 实现的对象,比如网络连接、数据库连接等,也可以在自定义类时通过接口 close() 来执行所需要的资源”清理”工作。

5. 总结

with 是对 try…expect…finally 语法的一种简化,并且提供了对于异常非常好的处理方式。在Python有2种方式来实现 with 语法:class-based 和 decorator-based,2种方式在原理上是等价的,可以根据具体场景自己选择。

with 最初起源于一种block…as…的语法,但是这种语法被很多人所唾弃,最后诞生了with,关于这段历史依然可以去参考PEP-343和PEP-340

with 主要用在:自定义上下文管理器来对软件系统中的资源进行管理,比如数据库连接、共享资源的访问控制等。文件操作。进程线程之间互斥对象。支持上下文其他对象

Python 中 with 用法详解相关推荐

  1. 【 Python 中 int 用法详解】(转载)

    Python 中 int 用法详解 欢迎转载,转载请注明出处! 文章目录 Python 中 int 用法详解 0. 参考资料 1. int 的无参数调用 2. int 接收数字作为参数 3. int ...

  2. Python 中 int 用法详解

    Python 中 int 用法详解 欢迎转载,转载请注明出处! 文章目录 Python 中 int 用法详解 0. 参考资料 1. int 的无参数调用 2. int 接收数字作为参数 3. int ...

  3. Python中self用法详解

    Python中self用法详解 https://blog.csdn.net/CLHugh/article/details/75000104 首页 博客 学院 下载 图文课 论坛 APP 问答 商城 V ...

  4. python中uuid用法详解

    uuid源码注解: r"""UUID objects (universally unique identifiers) according to RFC 4122.Thi ...

  5. Python中dict用法详解

    dict 称作字典(dictionary). 常见的形式 Dict = {'spam':1,'eggs':2}  单层key,value形式的字典 Dict = {'mans':{'jack':1,' ...

  6. Python中enumerate用法详解

    enumerate()是python的内置函数.适用于python2.x和python3.x enumerate在字典上是枚举.列举的意思 enumerate参数为可遍历/可迭代的对象(如列表.字符串 ...

  7. python中map用法详解_Python:map函数用法详解

    一个简单的例子:将一个list中所有元素平方,常规的做法如下图所示,虽然实现了这个功能,但并没有给人一目了然的感觉.若换成map来实现,则会好很多. 常规方法 map函数 1.map函数介绍及其简单使 ...

  8. Python中with用法详解

    一 .with语句的原理 上下文管理协议(Context Management Protocol):包含方法 __enter__()和__exit__(),支持该协议的对象要实现这两个方法. 上下文管 ...

  9. python中decimal用法详解

    decimal是python内置库. decimal模块支持快速正确四舍五入的十进制浮点运算. 示例代码: import decimala = decimal.Decimal(123) print(a ...

最新文章

  1. 面试大厂背怼!这都搞不定,你只能做“搬运工”!
  2. 汽车组装c语言排列组合算法,求一个关于排列组合的算法
  3. 日志间隔_在日志中搜索时间间隔
  4. JeecgBoot 单体升级微服务快速方案(十分钟搞定)
  5. 请确保此代码文件中定义的类与“inherits”属性匹配.并且该类扩展的基类(例如 Page 或 UserControl)是正确...
  6. Microsoft.Web.Mvc Assembly 说明
  7. OpenGL--纹理贴图基础
  8. 面试总结:Golang常见面试题汇总
  9. <论文阅读> M2BEV Multi-Camera Joint 3D Detection and Segmentation with Unified Bird’s-Eye View Represen
  10. 各大AI 开放平台一览
  11. babel : 无法加载文件 C:\Users\Lenovo\AppData\Roaming\npm\babel.ps1 ,因为在此系统上禁止运行脚本。
  12. 北京骑行者,改动的不只是通勤方式
  13. 爬虫遭遇状态码521的方法
  14. 【Codeforces Round#618 (Div. 2)】C. Anu Has a Function 题解
  15. python二进制数据存入数据库_python+ mysql存储二进制流的方式
  16. 画春天的景色计算机教案,中班美术教案《画春天》
  17. Tabu Search求解作业车间调度问题(Job Shop Scheduling)-附Java代码
  18. MATLAB 2013a 汉化画面
  19. 计算直线交点与夹角方法
  20. java毕业设计——基于java+J2EE+sqlserver的音像店租赁管理系统设计与实现(毕业论文+程序源码)——租赁管理系统

热门文章

  1. 从贪心选择到探索决策:基于强化学习的多样性排序
  2. XGB的python实现
  3. 论文学习8-How Question Generation Can Help Question Answering over Knowledge Base(KBQA-知识问答)
  4. tomcat的安装及配置
  5. android studio升级时提示 Connection failed. Please check your network connection and try again
  6. 顺序程序设计03 - 零基础入门学习C语言09
  7. linux的驱动开发——基于linux的单片机开发简介
  8. SpringBoot笔记整理(一)
  9. Matlab高光谱样本相关性分析
  10. jsp出现The import Xxx type is not resolved...