Plusieurs variables dans une instruction «avec»?

391

Est-il possible de déclarer plusieurs variables à l'aide d'une withinstruction 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 nettoyer deux ressources en même temps est-il le problème?

poisson-globe
la source
Peut-être comme ceci: avec [expr1, expr2] comme f: puis utilisez f [0] et f [1].
jbasko
Cela aurait été bien car pas besoin d'importer quelque chose .... mais cela ne fonctionne pas AttributeError: l'objet 'list' n'a pas d'attribut ' exit '
pufferfish
Si python venait de fermer, vous n'auriez pas besoin de l'instruction with
BT
Vous n'avez pas besoin d'utiliser une instruction with, non? Vous pouvez simplement définir file_out et file_in sur None, puis faire un essai / excepté / enfin où vous les ouvrez et les traitez dans l'essai, puis dans le fermez-les finalement s'ils ne sont pas None. Aucune double indentation nécessaire pour cela.
M Katz
1
Beaucoup de ces réponses ne traitent pas de la nécessité de plus de deux déclarations. Théoriquement, il peut y avoir des applications qui doivent ouvrir des dizaines de contextes, l'imbrication s'effondre très rapidement si des limites de longueur de ligne sont imposées.
ThorSummoner

Réponses:

667

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

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

Contrairement à la contextlib.nested, cela garantit que aet baura leur __exit__()appelé même siC() la __enter__()méthode ou lève une exception.

Vous pouvez également utiliser des variables antérieures dans des définitions ultérieures (h / t Ahmad ci-dessous):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)
Rafał Dowgird
la source
1
est-il possible de définir des champs égaux à quelque chose dans avec l'instruction comme dans with open('./file') as arg.x = file:?
Charlie Parker
13
Il est également possible: avec A () comme a, B (a) comme b, C (a, b) comme c:
Ahmad Yoosofan
test de classe 2: x = 1; t2 = test2 () avec open ('f2.txt') comme t2.x: pour l1 dans t2.x.readlines (): print (l1); # Charlie Parker # testé en python 3.6
Ahmad Yoosofan
1
s'il vous plaît noter, asest facultatif.
Sławomir Lenart
pour clarifier ce que @ SławomirLenart dit: asest requis si vous avez besoin de l'objet aou b, mais le tout as aou as bn'est pas requis
Ciprian Tomoiagă
56

contextlib.nested soutient ceci:

import contextlib

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

   ...

Mise à jour:
Pour citer la documentation, concernantcontextlib.nested :

Déconseillé depuis la version 2.7 : l'instruction with prend désormais directement en charge cette fonctionnalité (sans les bizarreries sujettes à confusion).

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

Alex Martelli
la source
34
Je suis désolé de le dire, mais je pense que le nestedgestionnaire de contexte est une erreur et ne doit jamais être utilisé. Dans cet exemple, si l'ouverture du deuxième fichier déclenche une exception, le premier fichier ne sera pas fermé du tout, détruisant ainsi totalement l'utilisation des gestionnaires de contexte.
Rafał Dowgird
Pourquoi dites vous cela? La documentation indique que l'utilisation de nested équivaut à nested 'with's
James Hopkin
@Rafal: un coup d'œil au manuel semble indiquer que python imbrique correctement les instructions with. Le vrai problème est que si le deuxième fichier lève une exception à la fermeture.
Inconnu
10
@James: Non, le code équivalent dans les documents sur docs.python.org/library/contextlib.html#contextlib.nested diffère des withblocs imbriqués standard . Les gestionnaires sont créés dans l'ordre avant d' entrer dans les blocs avec: m1, m2, m3 = A (), B (), C () Si B () ou C () échoue à l'exception, alors votre seul espoir de finaliser correctement A ( ) est le garbage collector.
Rafał Dowgird
8
Déconseillé depuis la version 2.7 . Remarque: l'instruction with prend désormais directement en charge cette fonctionnalité (sans les bizarreries source de confusion).
miku
36

Notez que si vous divisez les variables en lignes, vous devez utiliser des barres obliques inversées pour encapsuler 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 à la place un tuple.

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

Comme les tuples n'ont pas d' __enter__attribut, vous obtenez une erreur (non descriptive et n'identifie pas le type de classe):

AttributeError: __enter__

Si vous essayez d'utiliser asentre parenthèses, Python détecte 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.

nyanpasu64
la source
16

Je pense que vous voulez le faire à 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)
Andrew Hare
la source
5
C'est comme ça que je le fais actuellement, mais ensuite la nidification est deux fois plus profonde que je le veux (veux dire) ...
poisson
Je pense que c'est l'approche la plus propre - toute autre approche sera plus difficile à lire. La réponse d'Alex Martelli semble être plus proche de ce que vous voulez mais est beaucoup moins lisible. Pourquoi l'imbrication est-elle une telle préoccupation?
Andrew Hare
7
Ce n'est pas grave, certes, mais, par "importer ceci" (alias "Zen of Python"), "flat is better than nested" - c'est pourquoi nous avons ajouté contextlib.nested à la bibliothèque standard. BTW, 3.1 pourrait avoir une nouvelle syntaxe "avec A () comme a, B () comme b:" (le patch est en place, pas de déclaration BDFL à ce jour cependant) pour un support plus direct (donc clairement la solution de bibliothèque n'est pas ' t considéré comme parfait ... mais éviter l'imbrication indésirable est certainement un objectif largement partagé par les principaux développeurs Python).
Alex Martelli
2
@Alex: Très vrai mais il faut aussi considérer que "la lisibilité compte".
Andrew Hare,
4
@Andrew: Je pense qu'un niveau d'indentation exprime mieux la logique voulue du programme, qui est de créer "atomiquement" deux variables et de les nettoyer plus tard ensemble (je réalise que ce n'est pas vraiment ce qui se passe). Pensez que le problème des exceptions est un
facteur décisif
12

Depuis Python 3.3, vous pouvez utiliser la classe ExitStackdu contextlibmodule.

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

Le cas d'utilisation canonique mentionné dans la documentation gère 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)

Production:

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]
timgeb
la source
0

Dans Python 3.1+, vous pouvez spécifier plusieurs expressions de contexte, et elles seront traitées comme si plusieurs withinstructions étaient imbriquées:

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

est équivalent à

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

Cela signifie également que vous pouvez utiliser l'alias de la première expression dans la seconde (utile lorsque vous travaillez avec des connexions / curseurs db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Eugene Yarmash
la source