私が理解している限り、コンテキストマネージャの__init__()
および__enter__()
メソッドは、次々に正確に1回ずつ呼び出され、その間に他のコードが実行される可能性はありません。それらを2つの方法に分ける目的は何ですか?また、それぞれに何を入れるべきですか?
編集:申し訳ありませんが、ドキュメントに注意を払っていませんでした。
編集2:実際、混乱した理由は、@contextmanager
デコレータを考えていたからです。 @contextmananger
を使用して作成されたコンテキストマネージャーは1回しか使用できません(最初の使用後にジェネレーターが使い果たされるため)、多くの場合、with
ステートメント内のコンストラクター呼び出しで記述されます。それがwith
ステートメントを使用する唯一の方法であれば、私の質問は理にかなっているでしょう。もちろん、実際には、コンテキストマネージャーは@contextmanager
が作成できるものよりも一般的です。特に、コンテキストマネージャは一般に再利用できます。今回は正解だったらいいのに?
私が理解している限り、コンテキストマネージャの
__init__()
および__enter__()
メソッドは次々に正確に1回ずつ呼び出され、その間に他のコードが実行される可能性はありません。
そして、あなたの理解は間違っています。 __init__
はオブジェクトの作成時に呼び出され、__enter__
はwith
ステートメントで入力されたときに呼び出されます。これらは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
Antti Haapalasの答えはまったく問題ありません。引数の使用方法について少し詳しく説明したかったのです(myClass(* args)
など)。
with
ステートメントでクラスを初期化するために引数を使用することは、通常の方法でクラスを使用することと変わりません。呼び出しは次の順序で発生します。
__init__
(クラスの割り当て)__enter__
(コンテキストを入力)__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__
)、stackoverflow post here を参照してください。コメントには、その場合でも引数をどのように使用するかという質問に対する答えもあります。