解释 Python 的“__enter__”和“__exit__”

发布于 2024-08-16 07:12:33 字数 484 浏览 3 评论 0原文

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

    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

I saw this in someone's code. What does it mean?

    def __enter__(self):
        return self

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

Here is the complete code.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

停滞 2024-08-23 07:12:33

使用这些神奇方法(__enter____exit__)可以让您实现可轻松与 with 语句一起使用的对象。

这个想法是,它可以轻松构建需要执行一些“清理”代码的代码(将其视为 try-finally 块)。 这里有更多说明

一个有用的示例可能是数据库连接对象(一旦相应的“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 -- 'with' 语句' 也有一篇很好的文章。

Using these magic methods (__enter__, __exit__) allows you to implement objects which can be used easily with the with statement.

The idea is that it makes it easy to build code which needs some 'cleandown' code executed (think of it as a try-finally block). Some more explanation here.

A useful example could be a database connection object (which then automagically closes the connection once the corresponding 'with'-statement goes out of scope):

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()
        ...

As explained above, use this object with the with statement (you may need to do from __future__ import with_statement at the top of the file if you're on Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 -- The 'with' statement' has a nice writeup as well.

时光沙漏 2024-08-23 07:12:33

如果您知道上下文管理器是什么,那么您无需再了解__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.close) 属性来检查它 - 该属性返回 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__ 魔术方法有基本的了解。

If you know what context managers are then you need nothing more to understand __enter__ and __exit__ magic methods. Lets see a very simple example.

In this example I am opening the myfile.txt file with help of open function. The try/finally block ensures that even if an unexpected exception occurs myfile.txt will be closed.

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

Now I am opening same file with with statement:

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

If you look at the code, I didn't close the file & there is no try/finally block. Because with statement automatically closes myfile.txt . You can even check it by calling print(fp.closed) attribute -- which returns True.

This is because the file objects (fp in my example) returned by open function has two built-in methods __enter__ and __exit__. It is also known as context manager. __enter__ method is called at the start of with block and __exit__ method is called at the end.

Note: with statement only works with objects that support the context management protocol (i.e. they have __enter__ and __exit__ methods). A class which implement both methods is known as context manager class.

Now lets define our own context manager class.

 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")

I hope now you have basic understanding of both __enter__ and __exit__ magic methods.

情定在深秋 2024-08-23 07:12:33

我发现通过谷歌搜索很难找到 __enter____exit__ 方法的 python 文档,因此为了帮助其他人,这里有链接:

https://docs.python.org/2/reference/datamodel.html#with-statement -上下文管理器
https://docs.python.org/3/参考/datamodel.html#with-statement-context-managers
(两个版本的细节相同)

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

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

如果提供了异常,并且该方法希望抑制异常(即防止其传播),则它应该返回 true 值。否则,退出该方法时异常将被正常处理。

请注意,__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 是一个 Exception 实例。

大概 traceback 是一个 Python traceback 对象。

请注意,这里还有更多文档:
https://docs.python.org/3/library/ stdtypes.html#context-manager-types

...他们对 __enter____exit__ 方法有稍微更详细的解释。特别是更明确的是 __exit__ 应该返回一个布尔值(尽管 true 或 false 都可以正常工作,例如隐式 None 返回将给出传播异常的默认行为) 。

I found it strangely difficult to locate the python docs for __enter__ and __exit__ methods by Googling, so to help others here is the link:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(detail is the same for both versions)

object.__enter__(self)
Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

I was hoping for a clear description of the __exit__ method arguments. This is lacking but we can deduce them...

Presumably exc_type is the class of the exception.

It says you should not re-raise the passed-in exception. This suggests to us that one of the arguments might be an actual Exception instance ...or maybe you're supposed to instantiate it yourself from the type and value?

We can answer by looking at this article:
http://effbot.org/zone/python-with-statement.htm

For example, the following __exit__ method swallows any TypeError, but lets all other exceptions through:

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

...so clearly value is an Exception instance.

And presumably traceback is a Python traceback object.

Note that there are further docs here:
https://docs.python.org/3/library/stdtypes.html#context-manager-types

...they have a slightly more detailed explanation of the __enter__ and __exit__ methods. Particularly it is more explicit that __exit__ should return a boolean value (though truthy or falsy will work fine, e.g. an implicit None return will give the default behaviour of propagating the exception).

内心激荡 2024-08-23 07:12:33

除了上述示例调用顺序的答案之外,一个简单的运行示例

class MyClass:
    def __init__(self):
        print("__init__")

    def __enter__(self):
        print("__enter__")

    def __exit__(self, ex_type, ex_value, ex_traceback):
        print(f"ex_type, ex_value, ex_traceback={ex_type, ex_value, ex_traceback}")
        # if some errors occur, do special processing here
        if ex_type == KeyError:
            # raise the exception to be handled by upper calling functions
            return False
            # return True: ignore the exception

        # all other cases go to the default exit, e.g. do something to close resources, etc.
        print("__exit__")

    def __del__(self):
        print("__del__")

with MyClass():
    print("body")

    # # uncomment to simulate special Exceptions e.g. KeyError to process in __exit__()
    # raise KeyError("key error")

会产生输出:

__init__
__enter__
body
ex_type, ex_value, ex_traceback=(None, None, None)
__exit__
__del__

注意:当使用语法 with MyClass() as my_handler 时,变量 my_handler 获取 __enter__ 返回的值(),在上面的情况下!对于这样的使用,需要定义返回值,例如:

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

In addition to the above answers to exemplify invocation order, a simple run example

class MyClass:
    def __init__(self):
        print("__init__")

    def __enter__(self):
        print("__enter__")

    def __exit__(self, ex_type, ex_value, ex_traceback):
        print(f"ex_type, ex_value, ex_traceback={ex_type, ex_value, ex_traceback}")
        # if some errors occur, do special processing here
        if ex_type == KeyError:
            # raise the exception to be handled by upper calling functions
            return False
            # return True: ignore the exception

        # all other cases go to the default exit, e.g. do something to close resources, etc.
        print("__exit__")

    def __del__(self):
        print("__del__")

with MyClass():
    print("body")

    # # uncomment to simulate special Exceptions e.g. KeyError to process in __exit__()
    # raise KeyError("key error")

Produces the output:

__init__
__enter__
body
ex_type, ex_value, ex_traceback=(None, None, None)
__exit__
__del__

Note: when using the syntax with MyClass() as my_handler, variable my_handler gets the value returned by __enter__(), in the above case None! For such use, need to define return value, such as:

def __enter__(self): 
    print('__enter__')
    return self
清风挽心 2024-08-23 07:12:33

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

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

with语句的目的是隐藏tryfinally子句的流程控制,使代码难以理解。

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

服务器端的结果与之前相同

抱歉我的英语不好,我不清楚解释一下,谢谢....

try adding my answers (my thought of learning) :

__enter__ and [__exit__] both are methods that are invoked on entry to and exit from the body of "the with statement" (PEP 343) and implementation of both is called context manager.

the with statement is intend to hiding flow control of try finally clause and make the code inscrutable.

the syntax of the with statement is :

with EXPR as VAR:
    BLOCK

which translate to (as mention in 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)

try some code:

>>> 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'

and now try manually (following translate syntax):

>>> 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

the result of the server side same as before

sorry for my bad english and my unclear explanations, thank you....

苯莒 2024-08-23 07:12:33

这称为上下文管理器,我只想补充一点,其他编程语言也存在类似的方法。比较它们可能有助于理解 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 语句来实现相同的机制。这只是语法糖。

This is called context manager and I just want to add that similar approaches exist for other programming languages. Comparing them could be helpful in understanding the context manager in python.
Basically, a context manager is used when we are dealing with some resources (file, network, database) that need to be initialized and at some point, tear downed (disposed). In Java 7 and above we have automatic resource management that takes the form of:

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

Note that Session needs to implement AutoClosable or one of its (many) sub-interfaces.

In C#, we have using statements for managing resources that takes the form of:

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

In which Session should implement IDisposable.

In python, the class that we use should implement __enter__ and __exit__. So it takes the form of:

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

And as others pointed out, you can always use try/finally statement in all the languages to implement the same mechanism. This is just syntactic sugar.

没有你我更好 2024-08-23 07:12:33

当执行进入 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 提供的内容相匹配,这可以让您的生活变得更轻松。

Python calls __enter__ when execution enters the context of the with statement and it’s time to acquire the resource. When execution leaves the context again, Python calls __exit__ to free up the resource

Let's consider Context Managers and the “with” Statement in Python. Context Manager is a simple “protocol” (or interface) that your object needs to follow so it can be used with the with statement. Basically all you need to do is add enter and exit methods to an object if you want it to function as a context manager. Python will call these two methods at the appropriate times in the resource management cycle.

Let’s take a look at what this would look like in practical terms. Here’s how a simple implementation of the open() context manager might look like:

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()

Our ManagedFile class follows the context manager protocol and now supports the with statement.

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

Python calls enter when execution enters the context of the with statement and it’s time to acquire the resource. When execution leaves the context again, Python calls exit to free up the resource.

Writing a class-based context manager isn’t the only way to support the with statement in Python. The contextlib utility module in the standard library provides a few more abstractions built on top of the basic context manager protocol. This can make your life a little easier if your use cases matches what’s offered by contextlib.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文