2016-07-28 31 views
1

2つの非同期コンテキストマネージャが共通にネストされた方法で使用されていて、通常は2番目の結果のみが本体で使用されると仮定します。私たちが見つかった場合たとえば、自分自身がこのをたくさん入力:Python 3で非同期コンテキストマネージャをネストする方法

contextlib.nested
async with context_mgr_2() as cm2: 
    ...do something with cm2... 

を:私たちはちょうど行うことができるように

async with context_mgr_1() as cm1: 
    async with cm2.context_mgr_2() as cm2: 
     ...do something with cm2... 

はどのように我々は、これらのコンテキストマネージャをネストシングルコンテキストマネージャを作成することができますこれを非同期のコンテキストマネージャーのために使用していましたが、私はasyncioにそのようなヘルパーが見つかりませんでした。

+0

今まさか。 しかし、contextlib.ExitStackデザインの後にAsyncExitStackを実装することができます –

答えて

1

内部的には、これを使用して、非同期マネージャとして同期コンテキストマネージャと非同期コンテキストマネージャの両方をラップしています。 AsyncExitStackスタイルのプッシュセマンティクスと複数のマネージャーを単純にラッピングすることができます。

それはかなり十分にテストされていますが、私はテストを投稿したり、これをサポートすることを計画しておりませんので、ご自身の責任で使用して...

import asyncio 
import logging 
import sys 

from functools import wraps 

class AsyncContextManagerChain(object): 

    def __init__(self, *managers): 
     self.managers = managers 
     self.stack = [] 
     self.values = [] 

    async def push(self, manager): 
     try: 
      if hasattr(manager, '__aenter__'): 
       value = await manager.__aenter__() 
      else: 
       value = manager.__enter__() 

      self.stack.append(manager) 
      self.values.append(value) 
      return value 
     except: 
      # if we encounter an exception somewhere along our enters, 
      # we'll stop adding to the stack, and pop everything we've 
      # added so far, to simulate what would happen when an inner 
      # block raised an exception. 
      swallow = await self.__aexit__(*sys.exc_info()) 
      if not swallow: 
       raise 

    async def __aenter__(self): 
     value = None 

     for manager in self.managers: 
      value = await self.push(manager) 

     return value 

    async def __aexit__(self, exc_type, exc, tb): 
     excChanged = False 
     swallow = False # default value 
     while self.stack: 
      # no matter what the outcome, we want to attempt to call __aexit__ on 
      # all context managers 
      try: 
       swallow = await self._pop(exc_type, exc, tb) 
       if swallow: 
        # if we swallow an exception on an inner cm, outer cms would 
        # not receive it at all... 
        exc_type = None 
        exc = None 
        tb = None 
      except: 
       # if we encounter an exception while exiting, that is the 
       # new execption we send upward 
       excChanged = True 
       (exc_type, exc, tb) = sys.exc_info() 
       swallow = False 

     if exc is None: 
      # when we make it to the end, if exc is None, it was swallowed 
      # somewhere along the line, and we've exited everything successfully, 
      # so tell python to swallow the exception for real 
      return True 
     elif excChanged: 
      # if the exception has been changed, we need to raise it here 
      # because otherwise python will just raise the original exception 
      if not swallow: 
       raise exc 
     else: 
      # we have the original exception still, we just let python handle it... 
      return swallow 

    async def _pop(self, exc_type, exc, tb): 
    manager = self.stack.pop() 
    if hasattr(manager, '__aexit__'): 
     return await manager.__aexit__(exc_type, exc, tb) 
    else: 
     return manager.__exit__(exc_type, exc, tb) 
2

ケビンの答えはcontextlib.ExitStack従っていません3.5.2ではimplを使っていましたので、私は先に進んで、python 3.5.2の公式implに基づいて作成しました。何か問題が見つかったら、私はimplを更新します。

githubの要旨リンク:https://gist.github.com/thehesiod/b8442ed50e27a23524435a22f10c04a0

+0

私は編集提案に基づいてimplを更新し、__enter __/__ exit__もサポートしました – amohr

関連する問題