2016-09-21 70 views
7

據我所知,上下文管理器的方法每次一個接一個地被調用一次,沒有機會讓任何其他代碼在其間執行。將它們分爲兩種方法的目的是什麼?我應該把它們分別放入哪些方面?__init__ vs __enter__在上下文管理器

編輯:對不起,沒有注意到文檔。

編輯2:其實,我感到困惑的原因是因爲我在想着裝飾者@contextmanager。使用@contextmananger創建的上下文管理器只能使用一次(第一次使用後生成器將耗盡),因此經常在with語句中使用構造函數調用它們;如果這是使用with聲明的唯一方法,那麼我的問題就會有意義。當然,在現實中,上下文管理者比通常創建的更普遍;特別是情境管理者通常可以重複使用。我希望這次我明白了嗎?

+1

你很困惑*創建一個上下文管理器和輸入上下文。這兩者是截然不同的,您可以多次使用相同的上下文管理器。 –

回答

20

據我理解,上下文管理的__init__()__enter__()方法被調用一次每一個,一個又一個,不留下任何機會,任何其他代碼之間執行。

而你的理解是不正確的。當創建對象時調用__init__,當它與with語句一起輸入時,它們是__enter__,這些是2個完全不同的東西。通常情況下,構造函數在with初始化中直接調用,不需要插入代碼,但不一定是這種情況。

考慮這個例子:

class Foo: 
    def __init__(self): 
     print('__init__ called') 
    def __enter__(self): 
     print('__enter__ called') 
     return self 
    def __exit__(self, *a): 
     print('__exit__ called') 

myobj = Foo() 

print('\nabout to enter with 1') 
with myobj: 
    print('in with 1') 

print('\nabout to enter with 2') 
with myobj: 
    print('in with 2') 

myobj可以單獨被初始化,並在多個輸入with塊:

輸出:

__init__ called 

about to enter with 1 
__enter__ called 
in with 1 
__exit__ called 

about to enter with 2 
__enter__ called 
in with 2 
__exit__ called 

此外如果__init____enter__不分離,甚至不可能使用以下內容:

def open_etc_file(name): 
    return open(os.path.join('/etc', name)) 

with open_etc_file('passwd'): 
    ... 

因爲初始化(在open之內)明顯與with分開。


通過contextlib.manager創建的管理者單入的,但它們可以再次將with塊外構造。就拿例如:

from contextlib import contextmanager 

@contextmanager 
def tag(name): 
    print("<%s>" % name) 
    yield 
    print("</%s>" % name) 

您可以使用此爲:

def heading(level=1): 
    return tag('h{}'.format(level)) 

my_heading = heading() 
print('Below be my heading') 
with my_heading: 
    print('Here be dragons') 

輸出:

Below be my heading 
<h1> 
Here be dragons 
</h1> 

但是,如果你嘗試重用my_heading(因而,tag),你將得到

RuntimeError: generator didn't yield 
+1

哦,不知何故,我記得所有的例子都有''''''語句中的構造函數調用(例如'with Foo()...')。現在一切都很有意義。 Thx – max

+0

哦,等一下,那麼'@ contextmanager'呢?由於它依賴於一個發生器,不會在第一次使用時耗盡它,從而不可能重複使用該物體? – max

+0

@max是的,'contextmanager'用於一次性上下文管理器。 –

0

Antti Haapalas的回答非常好。我只是想闡述的論點(如myClass(* args))使用了一下,因爲這是有點我不清楚(我回顧展問自己爲什麼....)

使用論據,在with聲明初始化類不可不同於通常使用班級的方式。 的調用將按照下列順序發生:

  1. __init__(類的分配)
  2. __enter__(輸入上下文)
  3. __exit__(離開上下文)

簡單的例子:

class Foo: 
    def __init__(self, i): 
     print('__init__ called: {}'.format(i)) 
     self.i = i 
    def __enter__(self): 
     print('__enter__ called') 
     return self 
    def do_something(self): 
     print('do something with {}'.format(self.i)) 
    def __exit__(self, *a): 
     print('__exit__ called') 

with Foo(42) as bar: 
    bar.do_something() 

輸出:

__init__ called: 42 
__enter__ called 
    do something with 42 
__exit__ called 

如果您希望確保您的呼叫只能在上下文中使用(例如,強制呼叫__exit__),請參閱計算器後here。在評論中,你還會發現如何使用參數的問題的答案。

相關問題