【问题标题】:Explaining Python's '__enter__' and '__exit__'解释 Python 的 '__enter__' 和 '__exit__'
【发布时间】:2010-12-31 08:34:25
【问题描述】:

我在某人的代码中看到了这一点。什么意思?

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()

from __future__ import with_statement#for python2.5 

class a(object):
    def __enter__(self):
        print 'sss'
        return 'sss111'
    def __exit__(self ,type, value, traceback):
        print 'ok'
        return False

with a() as s:
    print s


print s

【问题讨论】:

  • @StevenVascellaro 编辑问题的代码通常不是一个好主意,尤其是在代码中有错误时。这个问题是在考虑 Py2 的情况下提出的,没有理由将其更新为 Py3。

标签: python oop with-statement contextmanager


【解决方案1】:

使用这些神奇的方法(__enter____exit__),您可以实现可以通过with 语句轻松使用的对象。

这个想法是,它使构建需要执行一些“清理”代码的代码变得容易(将其视为try-finally 块)。 Some more explanation here.

一个有用的例子可能是一个数据库连接对象(一旦相应的“with”语句超出范围,它就会自动关闭连接):

class DatabaseConnection(object):

    def __enter__(self):
        # make a database connection and return it
        ...
        return self.dbconn

    def __exit__(self, exc_type, exc_val, exc_tb):
        # make sure the dbconnection gets closed
        self.dbconn.close()
        ...

如上所述,将此对象与 with 语句一起使用(如果您使用的是 Python 2.5,则可能需要在文件顶部执行 from __future__ import with_statement)。

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 -- The 'with' statement' 也有一篇不错的文章。

【讨论】:

  • 可能__enter__ 应该总是返回self,因为只有在上下文中才能调用该类的其他方法。
  • @ViFI 在 PEP 343 中有 4 个 def __enter__(self) 的例子,但没有人这样做 return self: python.org/dev/peps/pep-0343 。你为什么这么认为?
  • @Grief :出于 2 个原因,在我看来,1)我将无法在 self 对象上调用其他方法,如下所述:stackoverflow.com/questions/38281853/… 2)self.XYZ 只是从维护的角度来看,self 对象的一部分和仅返回句柄对我来说似乎不合适。我宁愿返回句柄来完成对象,然后只为那些组件self对象提供公共API,我想像with open(abc.txt, 'r') as fin: content = fin.read()一样向用户公开这些对象@
  • 文件对象从__enter__返回self,这就是为什么你可以在f里面处理fwith open(...) as f
  • 我必须理解的一个微妙之处:如果对象需要参数来初始化,那么这些参数应该在 init 上,而不是 self
【解决方案2】:

如果您知道上下文管理器是什么,那么您无需再了解__enter____exit__ 魔术方法。让我们看一个非常简单的例子。

在本例中,我在 open 函数的帮助下打开 myfile.txt 文件。 try/finally 块确保即使发生意外异常,myfile.txt 也会被关闭。

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

现在我用 with 语句打开同一个文件:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line) 

如果您查看代码,我没有关闭文件并且没有 try/finally 块。因为 with 语句会自动关闭 myfile.txt 。你甚至可以通过调用print(fp.closed) 属性来检查它——它返回True

这是因为 open 函数返回的文件对象(在我的示例中为 fp)有两个内置方法 __enter____exit__。它也被称为上下文管理器。 __enter__ 方法在 with 块的开头调用,__exit__ 方法在末尾调用。

注意:with 语句仅适用于支持上下文管理协议的对象(即它们具有 __enter____exit__ 方法)。实现这两种方法的类称为上下文管理器类。

现在让我们定义我们自己的上下文管理器类。

 class Log:
    def __init__(self,filename):
        self.filename=filename
        self.fp=None    
    def logging(self,text):
        self.fp.write(text+'\n')
    def __enter__(self):
        print("__enter__")
        self.fp=open(self.filename,"a+")
        return self    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__")
        self.fp.close()

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
    print("Main")
    logfile.logging("Test1")
    logfile.logging("Test2")

希望您现在对__enter____exit__ 魔术方法有基本的了解。

【讨论】:

    【解决方案3】:

    我发现通过谷歌搜索找到 __enter____exit__ 方法的 python 文档非常困难,所以为了帮助其他人,这里是链接:

    https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
    https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
    (两个版本的细节相同)

    object.__enter__(self)
    输入与此对象相关的运行时上下文。 with 语句会将此方法的返回值绑定到语句的 as 子句中指定的目标(如果有)。

    object.__exit__(self, exc_type, exc_value, traceback)
    退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果上下文无异常退出,则所有三个参数都是None

    如果提供了一个异常,并且该方法希望抑制该异常(即阻止它被传播),它应该返回一个真值。否则,异常会在退出该方法时正常处理。

    注意__exit__() 方法不应重新引发传入的异常;这是调用者的责任。

    我希望能清楚地描述__exit__ 方法参数。这是缺乏的,但我们可以推断出来......

    大概exc_type是异常的类。

    它说您不应该重新引发传入的异常。这向我们表明,其中一个参数可能是一个实际的 Exception 实例……或者您应该自己根据类型和值来实例化它?

    我们可以看这篇文章来回答:
    http://effbot.org/zone/python-with-statement.htm

    例如,下面的 __exit__ 方法会吞下任何 TypeError,但允许所有其他异常通过:

    def __exit__(self, type, value, traceback):
        return isinstance(value, TypeError)
    

    ...很明显value 是一个异常实例。

    大概traceback 是一个Python traceback 对象。

    【讨论】:

    【解决方案4】:

    除了上面举例说明调用顺序的答案,一个简单的运行例子

    class myclass:
        def __init__(self):
            print("__init__")
    
        def __enter__(self): 
            print("__enter__")
    
        def __exit__(self, type, value, traceback):
            print("__exit__")
    
        def __del__(self):
            print("__del__")
    
    with myclass(): 
        print("body")
    

    产生输出:

    __init__
    __enter__
    body
    __exit__
    __del__
    

    提醒:使用with myclass() as mc语法时,变量mc获取__enter__()返回的值,如上例None!对于这样的使用,需要定义返回值,如:

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

    【讨论】:

    • 而且即使定义的顺序切换了,执行顺序也一样!
    【解决方案5】:

    这称为上下文管理器,我只想补充一点,其他编程语言也存在类似的方法。比较它们可能有助于理解 python 中的上下文管理器。 基本上,当我们处理一些需要初始化的资源(文件、网络、数据库)并在某些时候被拆除(处置)时,就会使用上下文管理器。在 Java 7 及更高版本中,我们采用以下形式的自动资源管理:

    //Java code
    try (Session session = new Session())
    {
      // do stuff
    }
    

    请注意,Session 需要实现AutoClosable 或其(许多)子接口之一。

    C# 中,我们使用以下形式来管理资源的 using 语句:

    //C# code
    using(Session session = new Session())
    {
      ... do stuff.
    }
    

    其中Session 应该实现IDisposable

    python中,我们使用的类应该实现__enter____exit__。所以它采取以下形式:

    #Python code
    with Session() as session:
        #do stuff
    

    正如其他人指出的那样,您始终可以在所有语言中使用 try/finally 语句来实现相同的机制。这只是语法糖。

    【讨论】:

      【解决方案6】:

      尝试添加我的答案(我的学习想法):

      __enter__[__exit__] 都是在进入和退出“with 语句”(PEP 343)的主体时调用的方法,两者的实现称为上下文管理器.

      with语句是为了隐藏try finally子句的流控,使代码难以理解。

      with 语句的语法是:

      with EXPR as VAR:
          BLOCK
      

      转换为(如 PEP 343 中所述):

      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)
      

      尝试一些代码:

      >>> import logging
      >>> import socket
      >>> import sys
      
      #server socket on another terminal / python interpreter
      >>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      >>> s.listen(5)
      >>> s.bind((socket.gethostname(), 999))
      >>> while True:
      >>>    (clientsocket, addr) = s.accept()
      >>>    print('get connection from %r' % addr[0])
      >>>    msg = clientsocket.recv(1024)
      >>>    print('received %r' % msg)
      >>>    clientsocket.send(b'connected')
      >>>    continue
      
      #the client side
      >>> class MyConnectionManager:
      >>>     def __init__(self, sock, addrs):
      >>>         logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
      >>>         : %(levelname)s --> %(message)s')
      >>>         logging.info('Initiating My connection')
      >>>         self.sock = sock
      >>>         self.addrs = addrs
      >>>     def __enter__(self):
      >>>         try:
      >>>             self.sock.connect(addrs)
      >>>             logging.info('connection success')
      >>>             return self.sock
      >>>         except:
      >>>             logging.warning('Connection refused')
      >>>             raise
      >>>     def __exit__(self, type, value, tb):
      >>>             logging.info('CM suppress exception')
      >>>             return False
      >>> addrs = (socket.gethostname())
      >>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      >>> with MyConnectionManager(s, addrs) as CM:
      >>>     try:
      >>>         CM.send(b'establishing connection')
      >>>         msg = CM.recv(1024)
      >>>         print(msg)
      >>>     except:
      >>>         raise
      #will result (client side) :
      2018-12-18 14:44:05,863         : INFO --> Initiating My connection
      2018-12-18 14:44:05,863         : INFO --> connection success
      b'connected'
      2018-12-18 14:44:05,864         : INFO --> CM suppress exception
      
      #result of server side
      get connection from '127.0.0.1'
      received b'establishing connection'
      

      现在手动尝试(按照翻译语法):

      >>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #make new socket object
      >>> mgr = MyConnection(s, addrs)
      2018-12-18 14:53:19,331         : INFO --> Initiating My connection
      >>> ext = mgr.__exit__
      >>> value = mgr.__enter__()
      2018-12-18 14:55:55,491         : INFO --> connection success
      >>> exc = True
      >>> try:
      >>>     try:
      >>>         VAR = value
      >>>         VAR.send(b'establishing connection')
      >>>         msg = VAR.recv(1024)
      >>>         print(msg)
      >>>     except:
      >>>         exc = False
      >>>         if not ext(*sys.exc_info()):
      >>>             raise
      >>> finally:
      >>>     if exc:
      >>>         ext(None, None, None)
      #the result:
      b'connected'
      2018-12-18 15:01:54,208         : INFO --> CM suppress exception
      

      服务器端的结果和之前一样

      对不起,我的英语不好,解释不清楚,谢谢....

      【讨论】:

        【解决方案7】:

        当执行进入 with 语句的上下文并且是时候获取资源时,Python 调用 __enter__。当执行再次离开上下文时,Python 调用__exit__ 来释放资源

        让我们考虑一下上下文管理器和 Python 中的“with”语句。上下文管理器是您的对象需要遵循的简单“协议”(或接口),因此它可以与 with 语句一起使用。基本上,如果您希望对象用作上下文管理器,您需要做的就是向对象添加 enterexit 方法。 Python 会在资源管理周期的适当时机调用这两个方法。

        让我们来看看实际情况会是什么样子。下面是 open() 上下文管理器的简单实现的样子:

        class ManagedFile:
            def __init__(self, name):
                self.name = name
        
            def __enter__(self):
                self.file = open(self.name, 'w')
                return self.file
        
            def __exit__(self, exc_type, exc_val, exc_tb):
                if self.file:
                    self.file.close()
        

        我们的 ManagedFile 类遵循上下文管理器协议,现在支持 with 语句。

        >>> with ManagedFile('hello.txt') as f:
        ...    f.write('hello, world!')
        ...    f.write('bye now')`enter code here`
        

        当执行进入 with 语句的上下文并且是时候获取资源时,Python 调用 enter。当执行再次离开上下文时,Python 调用 exit 来释放资源。

        编写基于类的上下文管理器并不是在 Python 中支持 with 语句的唯一方法。标准库中的 contextlib 实用程序模块提供了一些构建在基本上下文管理器协议之上的抽象。如果您的用例与 contextlib 提供的相匹配,这可以让您的生活更轻松。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-03-25
          • 2014-04-20
          • 1970-01-01
          • 1970-01-01
          • 2016-10-31
          • 2016-03-08
          • 2013-03-12
          相关资源
          最近更新 更多