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

Resuelto fmark asked hace 14 años • 11 respuestas

Hoy me encontré con la withdeclaració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. with¿Para qué está diseñ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 try..finallyusarlo with?
  4. ¿Por qué no se utiliza más ampliamente?
  5. ¿Qué clases de biblioteca estándar son compatibles con él?
fmark avatar Jun 10 '10 14:06 fmark
Aceptado
  1. Creo que esto ya ha sido respondido por otros usuarios antes que yo, así que solo lo agrego para que esté completo: 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 en sí misma un administrador de contexto, que le permite abrir un archivo, mantenerlo abierto mientras la ejecución sea en el contexto de la withdeclaración donde lo usó y cerrarlo tan pronto como salga del contexto. no importa si lo abandonó debido a una excepción o durante el flujo de control normal. Por lo tanto, la withdeclaración se puede utilizar de manera similar al patrón RAII en C++: la declaración adquiere algunos recursos withy los libera cuando se sale del withcontexto.

  2. Algunos ejemplos son: abrir archivos usando with open(filename) as fp:, adquirir bloqueos usando with lock:(donde lockhay una instancia de threading.Lock). También puedes construir tus propios administradores de contexto usando el contextmanagerdecorador de contextlib. Por ejemplo, suelo utilizar esto cuando tengo que cambiar el directorio actual temporalmente y luego volver 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 archivos sys.stdin, sys.stdouty sys.stderra algún otro identificador de archivos 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 por último, 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 avatar Jun 10 '2010 08:06 Tamás

Sugeriría dos conferencias interesantes:

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

1. La withdeclaración se utiliza para envolver 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 :

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

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

systempuntoout avatar Jun 10 '2010 07:06 systempuntoout

La withdeclaración de Python es un lenguaje integrado que admite el Resource Acquisition Is Initializationlenguaje comúnmente utilizado en C++. Su objetivo es permitir la adquisición y liberación segura de recursos del sistema operativo.

La withdeclaración crea recursos dentro de un alcance/bloque. Escribe su código utilizando 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 excepción).

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

Úselo siempre que adquiera recursos en su aplicación a los que deba renunciar explícitamente, como archivos, conexiones de red, bloqueos y similares.

Tendayi Mawushe avatar Jun 10 '2010 09:06 Tendayi Mawushe

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

Hago mucha informática científica y para algunas actividades necesito la Decimalbiblioteca para cálculos de precisión arbitraria. Alguna parte de mi código necesito alta precisión y para la mayoría de las otras partes necesito menos precisión.

Configuré mi precisión predeterminada en un número bajo y luego la 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 números grandes resultantes de factoriales. Cuando se realizan cálculos a escala genómica, hay que tener cuidado con los errores de redondeo y desbordamiento.

JudoWill avatar Jun 10 '2010 17:06 JudoWill

Un ejemplo de antipatrón podría ser utilizar el withinterior de un bucle cuando sería más eficiente tener el withexterior 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 uno, rowlo que puede causar problemas de rendimiento en comparación con la segunda forma, que abre y cierra el archivo solo una vez.

John La Rooy avatar Jun 10 '2010 10:06 John La Rooy