web-dev-qa-db-fra.com

Créer un bloc "avec" sur plusieurs gestionnaires de contexte?

Supposons que vous ayez acquis trois objets via le gestionnaire de contexte, par exemple Un verrou, une connexion à une base de données et une socket IP . Vous pouvez les acquérir de la manière suivante:

with lock:
   with db_con:
       with socket:
            #do stuff

Mais y a-t-il un moyen de le faire en un bloc? quelque chose comme

with lock,db_con,socket:
   #do stuff

De plus, est-il possible, étant donné un tableau de longueur inconnue d'objets ayant des gestionnaires de contexte, de faire en quelque sorte:

a=[lock1, lock2, lock3, db_con1, socket, db_con2]
with a as res:
    #now all objects in array are acquired

Si la réponse est "non", est-ce parce que la nécessité d'une telle fonctionnalité implique une mauvaise conception, ou peut-être devrais-je le suggérer dans un coup de pouce? :-P

149
olamundo

Dans Python 2.7 et 3.1 et au-dessus, vous pouvez écrire:

with A() as X, B() as Y, C() as Z:
    do_something()

C'est normalement la meilleure méthode à utiliser, mais si vous avez une liste de gestionnaires de contexte de longueur inconnue, vous aurez besoin de l'une des méthodes ci-dessous.


Dans Python 3.3, vous pouvez entrer une liste de gestionnaires de contexte de longueur inconnue à l’aide de contextlib.ExitStack :

with ExitStack() as stack:
    for mgr in ctx_managers:
        stack.enter_context(mgr)
    # ...

Cela vous permet de créer les gestionnaires de contexte à mesure que vous les ajoutez à la ExitStack, ce qui évite le problème possible avec contextlib.nested (mentionné ci-dessous).

contextlib2 fournit un backport de ExitStack pour Python 2.6 et 2.7.


Dans Python 2.6 et au-dessous, vous pouvez utiliser contextlib.nested :

from contextlib import nested

with nested(A(), B(), C()) as (X, Y, Z):
    do_something()

est équivalent à:

m1, m2, m3 = A(), B(), C()
with m1 as X:
    with m2 as Y:
        with m3 as Z:
            do_something()

Notez que ce n'est pas exactement la même chose que d'utiliser normalement with imbriqué, car A(), B() et C() seront tous appelés dans un premier temps avant d'entrer dans les gestionnaires de contexte. Cela ne fonctionnera pas correctement si l'une de ces fonctions déclenche une exception.

contextlib.nested est obsolète dans les nouvelles versions de Python au profit des méthodes ci-dessus.

268
interjay

La première partie de votre question est possible dans Python 3.1 .

Avec plus d'un élément, les gestionnaires de contexte sont traités comme si plusieurs déclarations avec étaient imbriquées:

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

est équivalent à

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

Modifié dans la version 3.1: prise en charge de plusieurs expressions de contexte

21
Mark Byers

La réponse de @ interjay est correcte. Toutefois, si vous avez besoin de le faire pour les gestionnaires de contexte longs, par exemple les gestionnaires de contexte mock.patch, vous réalisez rapidement que vous souhaitez le diviser sur plusieurs lignes. Il s'avère que vous ne pouvez pas les envelopper par deux, vous devez donc utiliser des barres obliques inverses. Voici à quoi ça ressemble:

with mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a, \
        mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b, \
        mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c:
    do_something()
14
sage88

La deuxième partie de votre question est résolue avec contextlib.ExitStack dans Python 3.3 .

8
Neil G

Suite à la réponse de @ sage88, vous pouvez toujours affecter à ces correctifs des noms de variables significatifs avant de les entrer.

Vous pouvez créer ces patchs sur plusieurs lignes 

a_patch = mock.patch('aaaaaaa') 
b_patch = mock.patch('bbbbbbb')
c_patch = mock.patch('ccccccc') 
with a_patch as a, b_patch as b, as c:    
    do_something()
0
poulter7