Explicando '__enter__' y '__exit__' de Python
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
El uso de estos métodos mágicos ( __enter__
, __exit__
) le permite implementar objetos que se pueden usar fácilmente con la with
declaració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-finally
bloque). 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 with
declaración (es posible que deba hacerlo from __future__ import with_statement
en 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.
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.
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. Lawith
declaració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ánNone
.
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_type
es 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 value
es una instancia de excepción.
Y presumiblemente traceback
es 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 None
devolución implícita dará el comportamiento predeterminado de propagar la excepción).
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
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 AutoClosable
una 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 Session
se 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.