¿Para qué está diseñada la declaración "con" de Python?

419

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

  1. ¿Para qué está withdiseñada la declaración de Python ?
  2. Para que lo usas?
  3. ¿Hay algún problema que deba tener en cuenta o antipatrones comunes asociados con su uso? ¿Algún caso en el que sea mejor usar try..finallyque with?
  4. ¿Por qué no se usa más ampliamente?
  5. ¿Qué clases de biblioteca estándar son compatibles con él?
fmark
fuente
55
Solo para el registro, aquí estáwith en la documentación de Python 3.
Alexey
viniendo de un fondo de Java, me ayuda a recordarlo como el "intento con recursos" correspondiente en Java, incluso si eso puede no ser del todo correcto.
vefthym

Respuestas:

399
  1. Creo que esto ya ha sido respondido por otros usuarios antes que yo, por lo que solo lo agrego en aras de la exhaustividad: la withdeclaración simplifica el manejo de excepciones al encapsular tareas comunes de preparación y limpieza en los llamados administradores de contexto . Se pueden encontrar más detalles en PEP 343 . Por ejemplo, la opendeclaración es un administrador de contexto en sí mismo, que le permite abrir un archivo, mantenerlo abierto siempre que la ejecución esté en el contexto de la withdeclaración donde lo usó y cerrarlo tan pronto como abandone el contexto, sin importar si lo ha dejado debido a una excepción o durante el flujo de control regular. Por lo tanto, la withdeclaración puede usarse de manera similar al patrón RAII en C ++: el recurso adquiere algunoswithdeclaración y publicado cuando abandonas el withcontexto.

  2. Algunos ejemplos son: abrir archivos usando with open(filename) as fp:, adquirir bloqueos usando with lock:(donde lockes una instancia de threading.Lock). También puede construir sus propios gestores de contexto utilizando el contextmanagerdecorador 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.stdouty sys.stderra algún otro identificador 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 contexto:

    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
Tamás
fuente
20
Gracias por agregar la comparación a RAII. Como programador de C ++ que me dijo todo lo que necesitaba saber.
Fred Thomsen
Bien, déjame aclarar esto. ¿Está diciendo que la withdeclaración está diseñada para llenar una variable con datos hasta que se completen las instrucciones y luego liberar la variable?
Musixauce3000
Porque lo usé para abrir un script py. with open('myScript.py', 'r') as f: pass. Que esperaba para ser capaz de llamar a la variable fpara ver el contenido del texto del documento, ya que esto es lo que aparecería si el documento se asigna a ftravés de un habitual opendeclaración: f = open('myScript.py').read(). Pero en cambio me dieron la siguiente: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Qué significa eso?
Musixauce3000
3
@ Musixauce3000: el uso withno elimina la necesidad readdel archivo real. Las withllamadas open, no sabe lo que necesita hacer con él, es posible que desee hacer una búsqueda, por ejemplo.
Tony Suffolk 66
@ Musixauce3000 La withdeclaración puede llenar una variable con datos o hacer algún otro cambio en el entorno hasta que se completen las instrucciones que contiene, y luego se realiza cualquier tipo de limpieza que sea necesaria. Los tipos de limpieza que se pueden hacer son cosas como cerrar un archivo abierto, o como lo ha hecho @Tamas en este ejemplo, cambiar los directorios a donde estaba antes, etc. Dado que Python tiene recolección de basura, liberar una variable no es importante caso de uso withgeneralmente se usa para otros tipos de limpieza.
Bob Steinke
89

Sugeriría dos conferencias interesantes:

  • PEP 343 La declaración "con"
  • Effbot Entendiendo la declaración "con" de Python

1. La withdeclaración se usa para ajustar la ejecución de un bloque con métodos definidos por un administrador de contexto. Esto permite try...except...finallyencapsular patrones de uso comunes 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 :

probar ... finalmente es bueno. con es mejor

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

systemmpuntoout
fuente
44
Realmente tiene su propio significado cuando se trata de objetos de sincronización de subprocesos. Relativamente raro en Python, pero cuando los necesita, realmente los necesita with.
detly
1
diveintopython.org está caído (¿permanentemente?). Reflejado
acurruca
Ejemplo de una buena respuesta, abrir archivo es un excelente ejemplo que muestra detrás de escena de abrir, io, cerrar las operaciones de archivo están ocultas limpiamente con un nombre de referencia personalizado
Angry 84
40

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

La withdeclaración crea recursos dentro de un ámbito / bloque. Escribe su código utilizando los recursos dentro del bloque. Cuando el bloque sale, los recursos se liberan sin importar el resultado del código en el bloque (es decir, si el bloque sale normalmente o debido a una excepción).

Muchos recursos en la biblioteca de Python que obedecen el protocolo requerido por la withdeclaración y, por lo tanto, se pueden usar de forma inmediata. Sin embargo, cualquiera puede crear recursos que se puedan utilizar en una declaración con implementando el protocolo bien documentado: PEP 0343

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

Tendayi Mawushe
fuente
27

Nuevamente para completar, agregaré mi caso de uso más útil para las withdeclaraciones.

Hago mucha informática científica y para algunas actividades necesito la Decimalbiblioteca para cálculos de precisión arbitrarios. En 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 lo uso withpara 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

Lo uso mucho con la prueba hipergeométrica que requiere la división de grandes números como factoriales de forma. Cuando hace cálculos a escala genómica, debe tener cuidado con los errores de redondeo y desbordamiento.

JudoWill
fuente
26

Un ejemplo de un antipatrón podría ser usar el withinterior de un bucle cuando sería más eficiente tener withel bucle externo

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 uno, lo rowque puede causar problemas de rendimiento en comparación con la segunda forma con abrir y cerrar el archivo una sola vez.

John La Rooy
fuente
10

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

... nueva declaración "with" al lenguaje Python para que sea posible factorizar los usos estándar de las declaraciones try / finally.

stefanB
fuente
5

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

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

cobbal
fuente
4

La declaració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 abandonar el bloque 'con'. Algunos de los elementos integrados de Python ya funcionan como gestores de contexto.

zefciu
fuente
3

Otro ejemplo de soporte inmediato, y uno que puede ser un poco desconcertante al principio cuando está acostumbrado a la forma en que se open()comporta incorporado , son connectionobjetos de módulos de bases de datos populares como:

Los connectionobjetos son administradores de contexto y, como tales, se pueden usar listos para usar en un with-statement, sin embargo, cuando se usa la nota anterior que:

Cuando with-blockfinaliza, ya sea con una excepción o sin ella, la conexión no se cierra . En caso de que with-blockfinalice con una excepción, la transacción se revierte, de lo contrario, se confirma la transacción.

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

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 cursorobjetos de psycopg2también son administradores de contexto. De la documentación relevante sobre el comportamiento:

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

bgse
fuente
3

En python, generalmente, la declaración " con " 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 declaración "con" 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 () sobre file-var.close ()

Tushar.PUCSD
fuente