web-dev-qa-db-fra.com

Plusieurs variables dans une instruction 'with'?

Est-il possible de déclarer plus d'une variable en utilisant une instruction with en Python?

Quelque chose comme:

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)

... ou le nettoyage de deux ressources en même temps est-il le problème?

331
pufferfish

C'est possible dans Python 3 depuis la v3.1 et Python 2.7 . La nouvelle with syntaxe prend en charge plusieurs gestionnaires de contexte:

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

Contrairement au contextlib.nested, cela garantit que a et b verront leur __exit__() appelée même si C() ou sa méthode __enter__() lève une exception.

577
Rafał Dowgird

contextlib.nested supporte ceci:

_import contextlib

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

   ...
_

Mise à jour:
Pour citer la documentation concernant contextlib.nested :

Obsolète depuis la version 2.7 : L'instruction with prend désormais en charge cette fonctionnalité directement (sans les bizarreries sources de confusion).

Voir réponse de Rafał Dowgird pour plus d'informations.

56
Alex Martelli

Notez que si vous divisez les variables en lignes, vous devez utiliser des barres obliques inverses pour envelopper les nouvelles lignes.

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

Les parenthèses ne fonctionnent pas car Python crée un Tuple à la place.

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

Comme les tuples n’ont pas d’attribut __enter__, vous obtenez une erreur (non descriptive et n’identifiant pas le type de classe):

AttributeError: __enter__

Si vous essayez d'utiliser as entre parenthèses, Python intercepte l'erreur au moment de l'analyse:

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

ErreurDeSyntaxe: Syntaxe invalide

https://bugs.python.org/issue12782 semble être lié à ce problème.

23
jimbo1qaz

Je pense que vous voulez faire cela à la place:

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)
18
Andrew Hare

Depuis Python3.3, vous pouvez utiliser la classe ExitStack du module contextlib .

Il peut gérer un nombre dynamique dynamique, ce qui signifie qu'il sera particulièrement utile si vous ne savez pas combien de fichiers vous allez gérer.

Le cas d'utilisation canonique mentionné dans la documentation est la gestion d'un nombre dynamique de fichiers.

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

Voici un exemple générique:

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)

Sortie:

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]
10
timgeb