Più variabili in un'istruzione 'with'?


391

È possibile dichiarare più di una variabile usando withun'istruzione in Python?

Qualcosa di simile a:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

... o il problema è ripulire due risorse contemporaneamente?


Forse in questo modo: con [expr1, expr2] come f: e quindi usa f [0] e f [1].
jbasko,

Sarebbe stato bello perché non è necessario importare qualcosa .... ma non funziona AttributeError: l'oggetto 'list' non ha attributo ' exit '
pesce palla

Se Python avesse solo delle chiusure, non avresti bisogno dell'istruzione with
BT

Non è necessario utilizzare un'istruzione with, giusto? Puoi semplicemente impostare file_out e file_in su None, quindi fare un tentativo / tranne / infine dove li apri ed elaborarli nel tentativo, quindi chiuderli infine se non sono Nessuno. Non è necessario alcun doppio rientro per questo.
M Katz,

1
Molte di queste risposte non affrontano la necessità di più di due dichiarazioni. Teoricamente potrebbero esserci applicazioni che devono aprire decine di contesti, l'annidamento si disintegra molto rapidamente se vengono imposte eventuali limitazioni di lunghezza della linea.
ThorSummoner,

Risposte:


667

È possibile in Python 3 dalla v3.1 e Python 2.7 . La nuova withsintassi supporta più gestori di contesto:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

A differenza di contextlib.nested, questo garantisce che ae bavranno il loro __exit__()nome anche se C()o il suo __enter__()metodo solleva un'eccezione.

Puoi anche usare le variabili precedenti nelle definizioni successive (h / t Ahmad sotto):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

1
è possibile impostare campi uguali a qualcosa dentro con l'istruzione come in with open('./file') as arg.x = file:?
Charlie Parker,

13
Inoltre, è possibile: con A () come a, B (a) come b, C (a, b) come c:
Ahmad Yoosofan,

class test2: x = 1; t2 = test2 () con open ('f2.txt') come t2.x: per l1 in t2.x.readlines (): print (l1); # Charlie Parker # testato in Python 3.6
Ahmad Yoosofan,

1
si prega di notare, asè facoltativo.
Sławomir Lenart,

per chiarire cosa dice @ SławomirLenart: asè necessario se hai bisogno dell'oggetto ao b, ma l'intero as ao as bnon è richiesto
Ciprian Tomoiagă

56

contextlib.nested supporta questo:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

Aggiornamento:
per citare la documentazione relativa a contextlib.nested:

Obsoleto dalla versione 2.7 : l'istruzione with ora supporta direttamente questa funzionalità (senza le stranezze a rischio di errori).

Vedi la risposta di Rafał Dowgird per maggiori informazioni.


34
Mi dispiace dirlo, ma penso che il nestedgestore del contesto sia un errore e non debba mai essere usato. In questo esempio, se l'apertura del secondo file solleva un'eccezione, il primo file non verrà affatto chiuso, distruggendo così totalmente lo scopo dell'utilizzo dei gestori di contesto.
Rafał Dowgird,

Perché dici così? La documentazione afferma che l'uso del nidificato equivale al nidificato "con"
James Hopkin,

@Rafal: uno sguardo al manuale sembra indicare che Python nidifica correttamente con le istruzioni. Il vero problema è se il secondo file genera un'eccezione alla chiusura.
Sconosciuto

10
@James: No, il codice equivalente nei documenti in docs.python.org/library/contextlib.html#contextlib.nested differisce dai withblocchi nidificati standard . I gestori vengono creati in ordine prima di inserire i blocchi with: m1, m2, m3 = A (), B (), C () Se B () o C () fallisce con l'eccezione, l'unica speranza di finalizzare correttamente A ( ) è il Garbage Collector.
Rafał Dowgird,

8
Obsoleto dalla versione 2.7 . Nota: l'istruzione with ora supporta direttamente questa funzionalità (senza le stranezze soggette a errori di confusione).
Miku,

36

Nota che se dividi le variabili in linee, devi usare le barre rovesciate per avvolgere le nuove linee.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Le parentesi non funzionano, poiché Python crea invece una tupla.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Poiché alle tuple manca un __enter__attributo, viene visualizzato un errore (non descrittivo e non identifica il tipo di classe):

AttributeError: __enter__

Se si tenta di utilizzare astra parentesi, Python rileva l'errore al momento dell'analisi:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError: sintassi non valida

https://bugs.python.org/issue12782 sembra essere collegato a questo problema.


16

Penso che tu voglia fare questo invece:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)

5
È così che lo faccio attualmente, ma poi l'annidamento è due volte più profondo di quello che voglio (cattivo) essere ...
pesce

Penso che questo sia l'approccio più pulito - qualsiasi altro approccio sarà più difficile da leggere. La risposta di Alex Martelli sembra essere più vicina a ciò che vuoi, ma è molto meno leggibile. Perché la nidificazione è così preoccupante?
Andrew Hare,

7
Non è un grosso problema, è vero, ma, per "import this" (alias "Zen of Python"), "flat è meglio del nidificato" - ecco perché abbiamo aggiunto contextlib.nested alla libreria standard. A proposito, 3.1 potrebbe avere una nuova sintassi "con A () come a, B () come b:" (la patch è dentro, nessuna pronuncia BDFL su di essa finora) per un supporto più diretto (quindi chiaramente la soluzione della libreria non è ' considerato perfetto ... ma evitare l'annidamento indesiderato è sicuramente un obiettivo ampiamente condiviso tra gli sviluppatori core di Python).
Alex Martelli,

2
@Alex: Molto vero, ma dobbiamo anche considerare che "La leggibilità conta".
Andrew Hare,

4
@Andrew: Penso che un livello di rientro esprima meglio la logica prevista del programma, che è quella di "atomicamente" creare due variabili e ripulirle più tardi insieme (mi rendo conto che in realtà non è quello che succede). Pensa che il problema dell'eccezione sia un
rompicapo

12

Da Python 3.3, puoi usare la classe ExitStackdal contextlibmodulo.

Può gestire un numero dinamico di oggetti sensibili al contesto, il che significa che si rivelerà particolarmente utile se non sai quanti file gestirai.

Il caso d'uso canonico menzionato nella documentazione sta gestendo un numero dinamico di file.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Ecco un esempio generico:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

Produzione:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]

0

In Python 3.1+ è possibile specificare più espressioni di contesto, che verranno elaborate come se withfossero nidificate più istruzioni:

with A() as a, B() as b:
    suite

è equivalente a

with A() as a:
    with B() as b:
        suite

Questo significa anche che puoi usare l'alias dalla prima espressione nella seconda (utile quando lavori con connessioni / cursori db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.