Pythonの「__enter__」と「__exit__」の説明


363

私はこれを誰かのコードで見ました。どういう意味ですか?

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()

from __future__ import with_statement#for python2.5 

class a(object):
    def __enter__(self):
        print 'sss'
        return 'sss111'
    def __exit__(self ,type, value, traceback):
        print 'ok'
        return False

with a() as s:
    print s


print s

19
ここでは良い説明:effbot.org/zone/python-with-statement.htm
Manur

7
@StevenVascellaro質問のコードを編集することは、特にコードにエラーがある場合は、一般に悪い考えです。この質問はPy2を考慮して行われたもので、Py3に更新する理由はありません。
jpaugh

回答:


420

これらのマジックメソッド(__enter____exit__)を使用すると、withステートメントで簡単に使用できるオブジェクトを実装できます。

アイデアは、いくつかの「クリーンダウン」コードを実行する必要があるコードを簡単にビルドできるようにするというものです(try-finallyブロックと考えてください)。ここでもう少し説明します

便利な例は、データベース接続オブジェクトです(対応する「with」ステートメントがスコープ外になると、自動的に接続を閉じます)。

class DatabaseConnection(object):

    def __enter__(self):
        # make a database connection and return it
        ...
        return self.dbconn

    def __exit__(self, exc_type, exc_val, exc_tb):
        # make sure the dbconnection gets closed
        self.dbconn.close()
        ...

上で説明したように、このオブジェクトをwithステートメントで使用します(from __future__ import with_statementPython 2.5を使用している場合は、ファイルの先頭で実行する必要がある場合があります)。

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343-「with」ステートメントも同様に優れた記述があります。


20
おそらく、クラスで他のメソッドのみがコンテキストで呼び出される可能性があるため、常に__enter__戻る必要selfがあります。
ViFI

3
@ViFI def __enter__(self)PEP 343には4つの例があり、誰も実行しませんreturn selfpython.org/dev/peps/pep-0343。どうしてそう思うの?
悲しみ2016

4
@Grief:2つの理由から、私の意見では、1)私は他のメソッドを呼び出すことができなくなりself、ここで説明したように、オブジェクトを:stackoverflow.com/questions/38281853/... 2)self.XYZがあるselfオブジェクトのほんの一部とそれだけにハンドルを返すことは、メンテナンスの観点からは私には不適切に思えます。私はむしろ、完全なオブジェクトへのハンドルを返し、その後、これらのコンポーネントのみに公開APIを提供することを好むself私はのようにユーザーに公開したい、オブジェクト with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
ファイルオブジェクトがselfから返されます__enter__。これが、ファイルをf内部として処理できるようになるwith open(...) as f
理由

2
私が理解しなければならない微妙な点の1つ:オブジェクトが初期化にパラメーターを必要とする場合、それらはselfではなくinitでなければなりません。
dfrankow 2017

70

コンテキストマネージャが何であるかを知っている場合は__enter____exit__魔法のメソッドを理解する必要はありません。非常に簡単な例を見てみましょう。

この例では、open関数を使用してmyfile.txt開いています。try /最後に予期しない例外が発生した場合でも性を保証ブロックmyfile.txtのは閉じられますが。

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

今、私はwithステートメントで同じファイルを開いています:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line) 

コードを見ると、ファイルを閉じていないため、try / finallyブロックはありません。そのため文が自動的に閉じ myfile.txtのをprint(fp.closed)属性を呼び出すことで確認することもできます-これは戻りますTrue

これは、open関数によって返されるファイルオブジェクト(この例ではfp)に2つの組み込みメソッド__enter__とがあるため__exit__です。コンテキストマネージャとも呼ばれます。__enter__methodはwithブロックの最初に__exit__ 呼び出され、methodは最後に呼び出されます。注:withステートメントは、コンテキスト管理プロトコルをサポートするオブジェクト(つまり、__enter__および__exit__メソッド)でのみ機能します。両方のメソッドを実装するクラスは、コンテキストマネージャクラスと呼ばれます。

次に、独自のコンテキストマネージャクラスを定義します。

 class Log:
    def __init__(self,filename):
        self.filename=filename
        self.fp=None    
    def logging(self,text):
        self.fp.write(text+'\n')
    def __enter__(self):
        print("__enter__")
        self.fp=open(self.filename,"a+")
        return self    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__")
        self.fp.close()

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
    print("Main")
    logfile.logging("Test1")
    logfile.logging("Test2")

両方__enter____exit__魔法の方法についての基本的な理解が得られたと思います。


53

Googlingによる__enter__とのpythonドキュメントを見つけるのが奇妙に難しい__exit__ので、他の人を助けるためにここにリンクがあります:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(詳細は両方のバージョンで同じです)

object.__enter__(self)
このオブジェクトに関連するランタイムコンテキストを入力します。with声明は、ターゲット(複数可)に、このメソッドの戻り値を結合する任意の場合は、文の句としてで指定されました。

object.__exit__(self, exc_type, exc_value, traceback)
このオブジェクトに関連するランタイムコンテキストを終了します。パラメータは、コンテキストが終了する原因となった例外を示します。コンテキストが例外なく終了した場合、3つの引数はすべてになりますNone

例外が提供され、メソッドが例外を抑制したい(つまり、伝播されないようにする)場合は、true値を返す必要があります。それ以外の場合、このメソッドの終了時に例外は通常どおり処理されます。

__exit__()メソッドは渡された例外を発生させてはならないことに注意してください。これは呼び出し側の責任です。

__exit__メソッドの引数の明確な説明を望んでいました。これは足りませんが、推測することができます...

おそらくexc_type例外のクラスです。

渡された例外を再発生させないようにと言っています。これは、引数の1つが実際のExceptionインスタンスである可能性があることを示唆しています...あるいは、タイプと値からインスタンス化する必要があるのでしょうか?

この記事を見て答えることができます:http :
//effbot.org/zone/python-with-statement.htm

たとえば、次の__exit__メソッドはTypeErrorを飲み込みますが、他のすべての例外は通過させます。

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

...明らかにvalue例外インスタンスです。

そしておそらくtracebackPython トレースバックオブジェクトです。


2
同意します。このURLは見つけるのが難しいです。
Shihao Xu

引数の使用に注意して、PEPリファレンス内のこの埋め込みビットに注意することが重要かもしれません:python.org/dev/peps/pep-0343/#generator-decorator
Tcll

43

呼び出し順序を例示するための上記の回答に加えて、簡単な実行例

class myclass:
    def __init__(self):
        print("__init__")

    def __enter__(self): 
        print("__enter__")

    def __exit__(self, type, value, traceback):
        print("__exit__")

    def __del__(self):
        print("__del__")

with myclass(): 
    print("body")

出力を生成します:

__init__
__enter__
body
__exit__
__del__

注意:上記の場合、構文を使用するとwith myclass() as mc、変数mcはによって返された値を取得します。このような用途では、次のような戻り値を定義する必要があります。__enter__()None

def __enter__(self): 
    print('__enter__')
    return self

3
また、定義の順番を入れ替えても実行順は変わりません!
Sean、

1
これはとても役に立ちました。ありがとうございました。
Reez0

5

私の答えを追加してみてください(私の学習の考え):

__enter__また、[__exit__]どちらも「withステートメント」(PEP 343)の本体への入口と出口で呼び出されるメソッドであり、両方の実装はコンテキストマネージャと呼ばれます。

withステートメントは、try finally句のフロー制御を隠して、コードをわかりにくくすることを目的としています。

withステートメントの構文は次のとおりです。

with EXPR as VAR:
    BLOCK

これは(PEP 343で言及されているように)変換されます。

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

いくつかのコードを試してください:

>>> import logging
>>> import socket
>>> import sys

#server socket on another terminal / python interpreter
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.listen(5)
>>> s.bind((socket.gethostname(), 999))
>>> while True:
>>>    (clientsocket, addr) = s.accept()
>>>    print('get connection from %r' % addr[0])
>>>    msg = clientsocket.recv(1024)
>>>    print('received %r' % msg)
>>>    clientsocket.send(b'connected')
>>>    continue

#the client side
>>> class MyConnectionManager:
>>>     def __init__(self, sock, addrs):
>>>         logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
>>>         : %(levelname)s --> %(message)s')
>>>         logging.info('Initiating My connection')
>>>         self.sock = sock
>>>         self.addrs = addrs
>>>     def __enter__(self):
>>>         try:
>>>             self.sock.connect(addrs)
>>>             logging.info('connection success')
>>>             return self.sock
>>>         except:
>>>             logging.warning('Connection refused')
>>>             raise
>>>     def __exit__(self, type, value, tb):
>>>             logging.info('CM suppress exception')
>>>             return False
>>> addrs = (socket.gethostname())
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> with MyConnectionManager(s, addrs) as CM:
>>>     try:
>>>         CM.send(b'establishing connection')
>>>         msg = CM.recv(1024)
>>>         print(msg)
>>>     except:
>>>         raise
#will result (client side) :
2018-12-18 14:44:05,863         : INFO --> Initiating My connection
2018-12-18 14:44:05,863         : INFO --> connection success
b'connected'
2018-12-18 14:44:05,864         : INFO --> CM suppress exception

#result of server side
get connection from '127.0.0.1'
received b'establishing connection'

そして、手動で試してください(翻訳構文に従ってください):

>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #make new socket object
>>> mgr = MyConnection(s, addrs)
2018-12-18 14:53:19,331         : INFO --> Initiating My connection
>>> ext = mgr.__exit__
>>> value = mgr.__enter__()
2018-12-18 14:55:55,491         : INFO --> connection success
>>> exc = True
>>> try:
>>>     try:
>>>         VAR = value
>>>         VAR.send(b'establishing connection')
>>>         msg = VAR.recv(1024)
>>>         print(msg)
>>>     except:
>>>         exc = False
>>>         if not ext(*sys.exc_info()):
>>>             raise
>>> finally:
>>>     if exc:
>>>         ext(None, None, None)
#the result:
b'connected'
2018-12-18 15:01:54,208         : INFO --> CM suppress exception

以前と同じサーバー側の結果

私の悪い英語と私の不明瞭な説明を申し訳ありません、ありがとう...


1

これはコンテキストマネージャと呼ばれ、他のプログラミング言語にも同様のアプローチが存在することを付け加えておきます。それらを比較することは、Pythonのコンテキストマネージャーを理解するのに役立ちます。基本的に、コンテキストマネージャーは、初期化する必要があるリソース(ファイル、ネットワーク、データベース)を処理し、ある時点で破棄(破棄)するときに使用されます。Java 7の上方に、私たちはの形式を取り自動リソース管理を持っています:

//Java code
try (Session session = new Session())
{
  // do stuff
}

Sessionは、AutoClosableまたはその(多くの)サブインターフェースの1つを実装する必要があることに注意してください。

ではC# 、我々は次の形式をとるリソースを管理するためのステートメントを使用しています:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}

Session実装する必要がありますIDisposable

python、我々が使用するクラスが実装する必要があります__enter____exit__。したがって、次の形式になります。

#Python code
with Session() as session:
    #do stuff

他の人が指摘したように、すべての言語で常にtry / finallyステートメントを使用して、同じメカニズムを実装できます。これは単なる構文上の砂糖です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.