Explicando '__enter__' y '__exit__' de Python

Resuelto zjm1126 asked hace 14 años • 7 respuestas

Vi esto en el código de alguien. ¿Qué significa?

    def __enter__(self):
        return self

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

Aquí está el código completo.

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
zjm1126 avatar Dec 31 '09 14:12 zjm1126
Aceptado

El uso de estos métodos mágicos ( __enter__, __exit__) le permite implementar objetos que se pueden usar fácilmente con la withdeclaración.

La idea es que facilita la creación de código que necesita la ejecución de algún código de "limpieza" (piense en ello como un try-finallybloque). Alguna explicación más aquí .

Un ejemplo útil podría ser un objeto de conexión de base de datos (que luego cierra automáticamente la conexión una vez que la declaración 'with' correspondiente sale del alcance):

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

Como se explicó anteriormente, use este objeto con la withdeclaración (es posible que deba hacerlo from __future__ import with_statementen la parte superior del archivo si está en Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343: La declaración 'con' también tiene una buena redacción.

ChristopheD avatar Dec 31 '2009 07:12 ChristopheD

Si sabe qué son los administradores de contexto , entonces no necesita nada más para comprender __enter__los __exit__métodos mágicos. Veamos un ejemplo muy sencillo.

En este ejemplo, estoy abriendo el archivo myfile.txt con la ayuda de la función abrir . El bloque try/finally garantiza que incluso si se produce una excepción inesperada, myfile.txt se cerrará.

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

Ahora estoy abriendo el mismo archivo con la declaración:

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

Si miras el código, no cerré el archivo y no hay ningún bloqueo try/finally . Porque con la declaración se cierra automáticamente myfile.txt . Incluso puedes comprobarlo llamando al print(fp.closed)atributo -- que devuelve True.

Esto se debe a que los objetos de archivo (fp en mi ejemplo) devueltos por la función open tienen dos métodos integrados __enter__y __exit__. También se le conoce como administrador de contexto. __enter__El método se llama al comienzo del bloque with y __exit__ el método se llama al final.

Nota: la instrucción with solo funciona con objetos que admiten el protocolo de gestión de contexto (es decir, tienen métodos __enter__y __exit__). Una clase que implementa ambos métodos se conoce como clase de administrador de contexto.

Ahora definamos nuestra propia clase de administrador de contexto .

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

Espero que ahora tengas conocimientos básicos de ambos __enter__y de __exit__los métodos mágicos.

N Randhawa avatar Jul 31 '2016 15:07 N Randhawa

Me resultó extrañamente difícil localizar los documentos __enter__y __exit__métodos de Python buscando en Google, así que para ayudar a otros aquí está el enlace:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(el detalle es el mismo para ambas versiones)

object.__enter__(self)
Ingrese el contexto de tiempo de ejecución relacionado con este objeto. La withdeclaración vinculará el valor de retorno de este método a los objetivos especificados en la cláusula as de la declaración, si corresponde.

object.__exit__(self, exc_type, exc_value, traceback)
Salga del contexto de tiempo de ejecución relacionado con este objeto. Los parámetros describen la excepción que provocó que se saliera del contexto. Si se salió del contexto sin excepción, los tres argumentos serán None.

Si se proporciona una excepción y el método desea suprimirla (es decir, evitar que se propague), debería devolver un valor verdadero. De lo contrario, la excepción se procesará normalmente al salir de este método.

Tenga en cuenta que __exit__()los métodos no deben volver a generar la excepción pasada; esta es responsabilidad de la persona que llama.

Esperaba una descripción clara de los __exit__argumentos del método. Esto falta pero podemos deducirlos...

Presumiblemente exc_typees la clase de la excepción.

Dice que no debes volver a generar la excepción pasada. Esto nos sugiere que uno de los argumentos podría ser una instancia de excepción real... ¿o tal vez se supone que debes crear una instancia tú mismo a partir del tipo y valor?

Podemos responder mirando este artículo:
http://effbot.org/zone/python-with-statement.htm

Por ejemplo, el siguiente __exit__método traga cualquier TypeError, pero deja pasar todas las demás excepciones:

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

... tan claramente valuees una instancia de excepción.

Y presumiblemente tracebackes un objeto de rastreo de Python .

Tenga en cuenta que hay más documentos aquí:
https://docs.python.org/3/library/stdtypes.html#context-manager-types

...tienen una explicación un poco más detallada de los métodos __enter__y __exit__. En particular, es más explícito que __exit__debería devolver un valor booleano (aunque verdadero o falso funcionará bien, por ejemplo, una Nonedevolución implícita dará el comportamiento predeterminado de propagar la excepción).

Anentropic avatar Jun 06 '2017 10:06 Anentropic

Además de las respuestas anteriores para ejemplificar el orden de invocación, un ejemplo de ejecución simple

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

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

    def __exit__(self, ex_type, ex_value, ex_traceback):
        print("__exit__")
    
    def __del__(self):
        print("__del__")
    
with MyClass(): 
    print("body")

Produce la salida:

__init__
__enter__
body
__exit__
__del__

Un recordatorio: cuando se utiliza la sintaxis with MyClass() as my_handler, la variable my_handler obtiene el valor devuelto por __enter__(), en el caso anterior None. Para tal uso, es necesario definir el valor de retorno, como por ejemplo:

def __enter__(self): 
    print('__enter__')
    return self
Yuri Feldman avatar Jan 08 '2018 16:01 Yuri Feldman

Esto se llama administrador de contexto y solo quiero agregar que existen enfoques similares para otros lenguajes de programación. Compararlos podría resultar útil para comprender el administrador de contexto en Python. Básicamente, un administrador de contexto se utiliza cuando se trata de algunos recursos (archivo, red, base de datos) que deben inicializarse y, en algún momento, eliminarse (eliminarse). En Java 7 y superiores tenemos una gestión automática de recursos que toma la forma de:

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

Tenga en cuenta que Session necesita implementar AutoClosableuna de sus (muchas) subinterfaces.

En C# , utilizamos declaraciones para administrar recursos que toman la forma de:

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

En el cual Sessionse debe implementar IDisposable.

En Python , la clase que usamos debe implementar __enter__y __exit__. Entonces toma la forma de:

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

Y como otros señalaron, siempre puedes usar la declaración try/finally en todos los idiomas para implementar el mismo mecanismo. Esto es sólo azúcar sintáctico.

Rohola Zandie avatar Feb 20 '2020 00:02 Rohola Zandie