¿Para qué está diseñada la declaración python "with"?


Me encontré con la declaración Python with por primera vez hoy. ¡He estado usando Python a la ligera durante varios meses y ni siquiera sabía de su existencia! Dado su estatus algo oscuro, pensé que valdría la pena preguntar:

  1. Qué es la instrucción Python with diseñado para ser utilizado?
  2. ¿Qué hacer utilizas?
  3. ¿Hay alguna tengo que ser consciente de, o anti-patrones comunes asociados con su uso? Cualquier caso en el que es mejor utilizar try..finally que with?
  4. ¿Por qué no se usa más ampliamente?
  5. ¿Qué clases de biblioteca estándar son compatibles con él?
Author: Wolf, 2010-06-10

10 answers

  1. Creo que esto ya ha sido respondido por otros usuarios antes que yo, por lo que solo lo agrego en aras de la integridad: la instrucción with simplifica el manejo de excepciones al encapsular tareas comunes de preparación y limpieza en los llamados gestores de contexto. Se pueden encontrar más detalles en PEP 343. Por ejemplo, la instrucción open es un administrador de contexto en sí mismo, que le permite abrir un archivo, mantenerlo abierto mientras la ejecución esté en el contexto de la with declaración donde la usó, y ciérrela tan pronto como salga del contexto, sin importar si la ha dejado debido a una excepción o durante el flujo de control regular. Por lo tanto, la instrucción with se puede usar de manera similar al patrón RAII en C++: la instrucción with adquiere algún recurso y lo libera cuando sale del contexto with.

  2. Algunos ejemplos son: abrir archivos usando with open(filename) as fp:, adquirir bloqueos usando with lock: (donde lock es una instancia de threading.Lock). También puede construir sus propios gestores de contexto usando el decorador contextmanager de contextlib. Por ejemplo, a menudo uso esto cuando tengo que cambiar el directorio actual temporalmente y luego regresar a donde estaba:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory
    

    Aquí hay otro ejemplo que redirige temporalmente sys.stdin, sys.stdout y sys.stderr a algún otro controlador de archivo y los restaura más tarde:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"
    

    Y finalmente, otro ejemplo que crea una carpeta temporal y la limpia al salir del context:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
    
 335
Author: Tamás,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-29 13:58:22

Yo sugeriría dos conferencias interesantes:

1. La instrucción with se usa para envolver la ejecución de un bloque con métodos definidos por un administrador de contexto. Esto permite que los patrones de uso comunes try...except...finally se encapsulen para una reutilización conveniente.

2. Podrías hacer algo. como:

with open("foo.txt") as foo_file:
    data = foo_file.read()

O

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

O (Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

O

lock = threading.Lock()
with lock:
    # Critical section of code

3. No veo ningún antipatrón aquí.
Citando Sumérgete en Python :

Inténtalo..finalmente es bueno. con es mejor.

4. Supongo que está relacionado con el hábito de los programadores de usar try..catch..finally declaraciones de otros lenguajes.

 79
Author: systempuntoout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 08:46:37

La instrucción Python with es soporte de lenguaje incorporado de la Resource Acquisition Is Initialization idioma comúnmente usado en C++. Su objetivo es permitir la adquisición y liberación segura de los recursos del sistema operativo.

La instrucción with crea recursos dentro de un ámbito/bloque. Escribes tu código usando los recursos dentro del bloque. Cuando el bloque sale, los recursos se liberan limpiamente independientemente del resultado del código en el bloque (es decir, si el bloque sale normalmente o debido a una salvedad).

Muchos recursos en la biblioteca de Python que obedecen el protocolo requerido por la instrucción with y por lo tanto se pueden usar con ella fuera de la caja. Sin embargo, cualquiera puede hacer recursos que se pueden usar en una declaración with implementando el protocolo bien documentado: PEP 0343

Úselo cada vez que adquiera recursos en su aplicación que deben ser renunciados explícitamente, como archivos, conexiones de red, bloqueos y similares.

 34
Author: Tendayi Mawushe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-28 16:48:27

Un ejemplo de un antipatrón podría ser usar el with dentro de un bucle cuando sería más eficiente tener el with fuera del bucle

Por ejemplo

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

Vs

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

La primera forma es abrir y cerrar el archivo para cada row que puede causar problemas de rendimiento en comparación con la segunda forma con abre y cierra el archivo solo una vez.

 24
Author: John La Rooy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 10:25:19

De nuevo para completar agregaré mi caso de uso más útil para las declaraciones with.

Hago mucha computación científica y para algunas actividades necesito la biblioteca Decimal para cálculos de precisión arbitrarios. Alguna parte de mi código necesito alta precisión y para la mayoría de las otras partes necesito menos precisión.

Establezco mi precisión predeterminada en un número bajo y luego uso with para obtener una respuesta más precisa para algunas secciones:

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

Uso esto mucho con la prueba Hipergeométrica lo que requiere la división de grandes números resultantes de factoriales de forma. Cuando realiza cálculos de escala genómica, debe tener cuidado con los errores de redondeo y desbordamiento.

 22
Author: JudoWill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 17:22:32

Ver PEP 343 - La instrucción 'with' , hay una sección de ejemplo al final.

... nueva declaración "with" a Python lenguaje para hacer es posible factorizar los usos estándar de las sentencias try/finally.

 9
Author: stefanB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 07:45:21

La instrucción with funciona con los llamados gestores de contexto:

Http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

La idea es simplificar el manejo de excepciones haciendo la limpieza necesaria después de salir del bloque 'with'. Algunos de los built-ins de python ya funcionan como gestores de contexto.

 3
Author: zefciu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 07:45:15

Los puntos 1, 2 y 3 están razonablemente bien cubiertos:

4: es relativamente nuevo, solo disponible en python2.6+ (o python2.5 usando from __future__ import with_statement)

 3
Author: cobbal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 08:34:42

En python generalmente la instrucción " with" se usa para abrir un archivo, procesar los datos presentes en el archivo, y también para cerrar el archivo sin llamar a un método close (). la instrucción "with" simplifica el manejo de excepciones al proporcionar actividades de limpieza.

Forma general de con:

with open(“file name”, “mode”) as file-var:
    processing statements

Nota: no es necesario cerrar el archivo llamando a close() en file-var.close ()

 1
Author: Tushar.PUCSD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-13 16:15:06

Otro ejemplo para el soporte listo para usar, y uno que podría ser un poco desconcertante al principio cuando está acostumbrado a la forma en que se comporta open() incorporado, son connection objetos de módulos de bases de datos populares como:

Los objetos connection son administradores de contexto y, como tales, se pueden usar fuera de la caja en un with-statement, sin embargo, cuando se usa lo anterior, tenga en cuenta que:

Cuando el with-block está terminada, ya sea con una excepción o sin, la conexión no está cerrada. En caso de que el with-block termine con una excepción, la transacción se revertirá, de lo contrario la transacción se comprometerá.

Esto significa que el programador tiene que tener cuidado de cerrar la conexión por sí mismo, pero permite adquirir una conexión, y usarla en múltiples with-statements, como se muestra en los psycopg2 docs :

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

En el ejemplo anterior, notará que los objetos cursor de psycopg2 también son gestores de contexto. De la documentación relevante sobre el comportamiento:

Cuando un cursor sale del with-block se cierra, liberando cualquier recurso eventualmente asociado con él. El estado de la transacción no se ve afectado.

 0
Author: bgse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-28 23:02:04