python中的__enter__ __

  • 2020 年 1 月 19 日
  • 筆記

我们前面文章介绍了迭代器和可迭代对象,这次介绍python的上下文管理。在python中实现了__enter__和__exit__方法,即支持上下文管理器协议。上下文管理器就是支持上下文管理器协议的对象,它是为了with而生。当with语句在开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法 with的语法:

with EXPR as VAR:      BLOCK

这是上面语法的伪代码:

mgr = (EXPR)  exit = type(mgr).__exit__  # Not calling it yet  value = type(mgr).__enter__(mgr)  exc = True  try:      try:          VAR = value  # Only if "as VAR" is present          BLOCK      except:          # The exceptional case is handled here          exc = False          if not exit(mgr, *sys.exc_info()):              raise          # The exception is swallowed if exit() returns true  finally:      # The normal and non-local-goto cases are handled here      if exc:          exit(mgr, None, None, None)

1、生成上下文管理器mgr 2、如果没有发现__exit__, __enter__两个方法,解释器会抛出AttributeError异常 3、调用上下文管理器的 __enter__() 方法 4、如果语法里的as VAR没有写,那么 伪代码里的 VAR= 这部分也会同样被忽略 5、如果BLOCK中的代码正常结束,或者是通过break, continue ,return 来结束,__exit__()会使用三个None的参数来返回 6、如果执行过程中出现异常,则使用 sys.exc_info的异常信息为参数调用 __exit__(exc_type, exc_value, exc_traceback) 之前我们对文件的操作是这样的:

try:      f = open('filename')  except:      print("Unexpected error:", sys.exc_info()[0])  else:      print(f.readlines())      f.close()

现在有了with语句可以使代码更加简洁,减少编码量,下面的语句会在执行完后自动关闭文件(即使出现异常也会)。:

with open('example.info', 'r') as f:      print(f.readlines())

一个例子:

class TmpTest:      def __init__(self,filename):          self.filename=filename      def __enter__(self):          self.f = open(self.filename, 'r')         # return self.f      def __exit__(self, exc_type, exc_val, exc_tb):          self.f.close()    test=TmpTest('file')    with test as t:      print ('test result: {}'.format(t))

返回:

test result: None

这个例子里面__enter__没有返回,所以with语句里的"as t"到的是None,修改一下上面的例子:

class TmpTest:      def __init__(self,filename):          self.filename=filename      def __enter__(self):          self.f = open(self.filename, 'r')          return self.f      def __exit__(self, exc_type, exc_val, exc_tb):          self.f.close()    test=TmpTest('file')    with test as t:      print ('test result: {}'.format(t))

返回:

test result: <_io.TextIOWrapper name='file' mode='r' encoding='cp936'>

如果在__init__或者__enter__中抛出异常,则不会进入到__exit__中:

class TmpTest:      def __init__(self,filename):          self.filename=filename          print("__init__")          raise ImportError      def __enter__(self):          self.f = open(self.filename, 'r')          print("__enter__")          return self.f      def __exit__(self, exc_type, exc_val, exc_tb):          print("__exit__")          self.f.close()    test=TmpTest('file')  with test as t:      print ('test result: {}'.format(t))

返回:

__init__  Traceback (most recent call last):    File "D:/pythonScript/leetcode/leetcode.py", line 14, in <module>      test=TmpTest('file')    File "D:/pythonScript/leetcode/leetcode.py", line 5, in __init__      raise ImportError  ImportError

如果在__exit__中返回True,则不会产生异常:

class TmpTest:      def __init__(self,filename):          self.filename=filename          print("__init__")        def __enter__(self):          self.f = open(self.filename, 'r')          print("__enter__")          return self.f        def __exit__(self, exc_type, exc_val, exc_tb):          print("__exit__ {} ".format(exc_type))          self.f.close()          return True    test=TmpTest('file')  with test as t:      print ('test result: {}'.format(t))      raise ImportError  print("no error")

返回:

__init__  __enter__  test result: <_io.TextIOWrapper name='file' mode='r' encoding='cp936'>  __exit__ <class 'ImportError'>  no error

参考: https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p03_make_objects_support_context_management_protocol.html?highlight=with         https://docs.python.org/3/library/stdtypes.html#typecontextmanager         https://www.python.org/dev/peps/pep-0343/