2017-04-18 50 views
0

我想嘗試使用python with塊來將修飾符應用於該塊內的操作。但我不確定在協同程序的存在下是否可以做到這一點。Python`with`上下文vs生成器/協同程序/任務

例如,假設我有一個WithContext對象暫時推到堆棧上是這樣的:

class WithContext: 
    stack = [] 
    def __init__(self, val): 
     self.val = val 
    def __enter__(self): 
     WithContext.stack.append(self.val) 
    def __exit__(self, exc_type, exc_val, exc_tb): 
     WithContext.stack.pop() 
def do_scoped_contextual_thing(): 
    print(WithContext.stack[-1]) 

(顯然堆疊構件將必須是線程局部,但忽略現在)

那麼這個代碼:

with WithContext("a"): 
    do_scoped_contextual_thing() 
    with WithContext("b"): 
     do_scoped_contextual_thing() 
with WithContext("c"): 
    do_scoped_contextual_thing() 

會打印:

a 
b 
c 

但現在假設我有一個協同程序情況:

def coroutine(): 
    with WithContext("inside"): 
     yield 1 
     do_scoped_contextual_thing() 
     yield 2 

with WithContext("outside"): 
    for e in coroutine(): 
     do_scoped_contextual_thing() 
     print("got " + str(e)) 

這段代碼的輸出:

outside 
got 1 
inside 
outside 
got 2 

但實際上它會輸出:

inside 
got 1 
inside 
inside 
got 2 

的外面因爲__enter__裏面的內容而改成內側他的協同程序在棧頂放置了一個值,並且在協程結束之前不調用__exit__(而不是在你跳入和退出協程時不斷地進入和退出)。

有沒有辦法解決這個問題?是否存在「協同本地」變量?

+0

沒有全局可變狀態('WithContext.stack' here);代之以傳入。在協程(堆棧)中用於e:do_scoped_contextual_thing(stack)'其中'stack'是不可變的。 – Ryan

+1

您是否考慮過上下文結果的訪問者?像WithContext('outside')這樣的外部:'...'outside.do_scoped_contextual_thing()'? –

+0

@Ryan是的,我知道它的缺點。我仍然想試驗它。 –

回答

0

一個可能的半破解「解決方案」是將上下文與堆棧框架的位置相關聯,並在查找上下文時檢查該位置。

class WithContext: 
    _stacks = defaultdict(list) 

    def __init__(self, val): 
     self.val = val 

    def __enter__(self): 
     _, file, _, method, _, _ = inspect.stack()[1] 
     WithContext._stacks[(file, method)].append(self.val) 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     _, file, _, method, _, _ = inspect.stack()[1] 
     WithContext._stacks[(file, method)].pop() 

    @staticmethod 
    def get_context(): 
     for frame in inspect.stack()[1:]: 
      _, file, _, method, _, _ = frame 
      r = WithContext._stacks[(file, method)] 
      if r: 
       return r[-1] 
     raise ValueError("no context") 

注意不斷地尋找了堆棧幀不僅僅是身邊掠過值比較昂貴,而且,你可能不想告訴你寫了這個人。

請注意,這將仍然在更復雜的情況下打破。

例如:

  • 如果同樣的方法是在棧上兩次?
  • 如果生成器從一個地方迭代了一點,然後再從另一個地方多一點呢?
  • 遞歸生成器呢?
  • 異步方法呢?
1

我對此感覺不太好,但我確實修改了測試代碼以重新輸入協程幾次。類似於@ CraigGidney的解決方案,它使用inspect模塊訪問並緩存調用堆棧(又名「範圍」)中的信息,其中創建了一個WithContext對象。

然後,我基本上搜索堆棧尋找一個緩存值,並使用id函數來嘗試並避免保留對實際框架對象的引用。

import inspect 

class WithContext: 
    stack = [] 
    frame_to_stack = {} 
    def __init__(self, val): 
     self.val = val 
    def __enter__(self): 
     stk = inspect.stack(context=3) 
     caller_id = id(stk[1].frame) 
     WithContext.frame_to_stack[caller_id] = len(WithContext.stack) 
     WithContext.stack.append((caller_id, self.val)) 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     wc = WithContext.stack.pop() 
     del WithContext.frame_to_stack[wc[0]] 

def do_scoped_contextual_thing(): 
    stack = inspect.stack(context=0) 
    f2s = WithContext.frame_to_stack 

    for f in stack: 
     wcx = f2s.get(id(f.frame)) 

     if wcx is not None: 
      break 
    else: 
     raise ValueError("No context object in scope.") 

    print(WithContext.stack[wcx][1]) 

def coroutine(): 
    with WithContext("inside"): 
     for n in range(3): 
      yield 1 
      do_scoped_contextual_thing() 
      yield 2 

with WithContext("outside"): 
    for e in coroutine(): 
     do_scoped_contextual_thing() 
     print("got " + str(e)) 
相關問題