2017-05-30 66 views
1

考慮下面的代碼:壓扁`with`和`嘗試/ finally`在Python

async with app: 
    ... 
    async with app.resource as rsrc: 
     ... 
     async with rsrc.foo as bar: 
      ... 

有很多嵌套withasync with語句對的代碼的可讀性的負面影響,尤其是在測試中,在相同的條款可能會重複使用很多次。

在像D這樣的語言中,有scope(exit) ...結構,它允許您將代碼附加到作用域終結器列表 - 這個代碼將在作用域離開後執行,允許您有效地執行__exit__所做的操作,但不添加縮進。

有沒有辦法在Python中壓扁with並執行類似操作?

await async_with(app) 
scope_exit(app.quit) 

... 

rsrc = await async_with(app.resource) 
scope_exit(rsrc.release) 

... 

bar = await async_with(rsrc.foo) 
scope_exit(lambda: bar.unfrob()) 

... 

或者,或者,有沒有辦法在退出範圍時可靠地執行任意代碼?

+0

['with'語句](https://docs.python.org/3/reference/compound_stmts.html#with)可以有許多成員。我不確定您是否可以在聲明中使用任何結果變量。 – Kendas

+2

@Kendas,雖然可以在單個'with'語句中包含多個子句,但是無法使用'with'(或不存在的'scope_exit')執行任何代碼是不可能的 – toriningen

回答

0

據我所知,你不能達到你想要的。特別是因爲不可能定義這樣的「範圍」。

即使在您的僞代碼:

await async_with(app) 
scope_exit(app.quit) 

... 

rsrc = await async_with(app.resource) 
scope_exit(rsrc.release) 

... 

bar = await async_with(rsrc.foo) 
scope_exit(lambda: bar.unfrob()) 

raise ExceptionHere() 

什麼情況下應[不]抓住它?

我可能是錯的,但設置用於捕捉異常的「警衛」的唯一方法是try/except子句。 with大致是它的一個包裝。

說到動機,如果你遇到嵌套上下文管理器如此困難的問題,你應該重構你的核心以提取內部上下文作爲函數。你可以看看contextlib的助手

+0

如果您在最後,它應該遵循與嵌套'with'一樣的展開流程 - 例如應調用'bar.unfrob()',然後調用'rsrc.release()',然後調用'app.quit()'。如果在'scope_exit(lambda:bar.unfrob())'之前引發異常,那麼就不會有這樣的範圍終結器,所以只應調用'rsrc.release()'和'app.quit()'。 – toriningen

+0

也許我應該把它變成一個PEP來定義「範圍終結器」的概念,以在Python中啓用顯式的RAII。 – toriningen

+0

Tbh,我真的懷疑這種東西扁平化的可能性,但你可以隨時嘗試:) 我推薦以下讀物: https://www.python.org/dev/peps/pep-0310/(其實,父親'with',afaik) https://www.python。org/dev/peps/pep-0343 /(直覺用於') https://stackoverflow.com/a/5071376/2161778(RAII在python和思想中) – Slam

0

我無法忍受從with聲明額外的缺口...這是我做的扁平化他們:

嵌套:

def stuff_with_files(path_a, path_b): 
    # ... do stuff 
    with open(path_a) as fileA: 
     # ... do stuff with fileA 
     with open(path_b) as fileB: 
      # ... do stuff with fileA and fileB 

def main(): 
    stuff_with_files('fileA.txt', 'fileB.txt') 

展開:

# Extract the 'with' statement. 
def with_file(path, func, *args, **kwargs): 
    with open(path) as file: 
     func(file, *args, **kwargs) 


def process_a_b(fileB, results_of_a): 
    # ... do stuff with fileB and results_of_a. 


def process_a(fileA): 
    # ... do stuff with fileA 
    results = # ... 
    with_file('fileB.txt', process_a_b, results) 


def main(): 
    with_file('fileA.txt', process_a)