Python 中 with用法及原理


前言

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。

问题引出

如下代码:

file = open("1.txt")
data = file.read()
file.close()

上面代码存在2个问题:
(1)文件读取发生异常,但没有进行任何处理;
(2)可能忘记关闭文件句柄;

改进

try:
    f = open('xxx')
except:
    print('fail to open')
    exit(-1)
try:
    do something
except:
    do something
finally:
    f.close()

虽然这段代码运行良好,但比较冗长。
而使用with的话,能够减少冗长,还能自动处理上下文环境产生的异常。如下面代码:

with open("1.txt") as file:
    data = file.read()

with 工作原理

(1)紧跟with后面的语句被求值后,返回对象的 –enter–() 方法被调用,这个方法的返回值将被赋值给as后面的变量;

(2)当with后面的代码块全部被执行完之后,将调用前面返回对象的 –exit–() 方法。

with工作原理代码示例:

class Sample:
    def __enter__(self):
        print "in __enter__"
        return "Foo"
    def __exit__(self, exc_type, exc_val, exc_tb):
        print "in __exit__"
def get_sample():
    return Sample()
with get_sample() as sample:
    print "Sample: ", sample

运行结果:

in __enter__
Sample:  Foo
in __exit__

整个运行过程如下:

(1)enter()方法被执行;
(2)enter()方法的返回值,在这个例子中是”Foo”,赋值给变量sample;
(3)执行代码块,打印sample变量的值为”Foo”;
(4)exit()方法被调用;

【注】exit()方法中有3个参数, exc_type, exc_val, exc_tb,这些参数在异常处理中相当有用。
exc_type: 错误的类型
exc_val: 错误类型对应的值
exc_tb: 代码中错误发生的位置

示例代码:

class Sample():
    def __enter__(self):
        print('in enter')
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print "type: ", exc_type
        print "val: ", exc_val
        print "tb: ", exc_tb
    def do_something(self):
        bar = 1 / 0
        return bar + 10
with Sample() as sample:
    sample.do_something()

输出结果:

in enter
Traceback (most recent call last):
type:  <type 'exceptions.ZeroDivisionError'>
val:  integer division or modulo by zero
  File "/home/user/cltdevelop/Code/TF_Practice_2017_06_06/with_test.py", line 36, in <module>
tb:  <traceback object at 0x7f9e13fc6050>
    sample.do_something()
  File "/home/user/cltdevelop/Code/TF_Practice_2017_06_06/with_test.py", line 32, in do_something
    bar = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Process finished with exit code 1

总结

实际上,在with后面的代码块抛出异常时,exit()方法被执行。开发库时,清理资源,关闭文件等操作,都可以放在exit()方法中。

总之,with-as表达式极大的简化了每次写finally的工作,这对代码的优雅性是有极大帮助的。

如果有多项,可以这样写:

With open('1.txt') as f1, open('2.txt') as  f2:
    do something

with语句的原理

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

with语句的常用表达式:

with EXPR as VAR:  # EXPR可以是任意表达式
    BLOCK

其一般的执行过程是这样的:

1、执行EXPR,生成上下文管理器context_manager;

2、获取上下文管理器的 __exit()__ 方法,并保存起来用于之后的调用;

3、调用上下文管理器的 __enter__() 方法;如果使用了as子句,则将 __enter__() 方法的返回值赋值给as子句中的VAR;

4、执行BLOCK中的表达式;

5、不管是否执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法, __exit__() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)

6、出现异常时,如果__exit__(type, value, traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。

自定义上下文管理器

 Pythonwith语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。

示例1:

# coding = utf-8
# 2019/7/19  Luckyxxt:有趣的事,Python永远不会缺席!
#!/usr/bin/env python

class DBManager(object):
    def __init__(self):
        pass

    def __enter__(self):
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')
        return True

def getInstance():
        return DBManager()

with getInstance() as dbManagerIns:
    print('with demo')

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

代码运行结果如下:

'''
__enter__
with demo
__exit__

'''

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

 

示例2:

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 f:
    print(f)
    print('hello with')

运行结果:

'''
enter called
xxt
hello with
exit called

'''

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM