web-dev-qa-db-ja.com

Pythonの '__enter__'と '__exit__'の説明

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

    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
276
zjm1126

これらのマジックメソッド(__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ステートメントと共に使用します(Python 2.5を使用している場合は、ファイルの先頭でfrom __future__ import with_statementを実行する必要があるかもしれません)。

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - 'with'文 ' にもNiceの記事があります。

331
ChristopheD

あなたがコンテキストマネージャが何かを知っていれば、あなたは__enter____exit__マジックメソッドを理解するためにこれ以上必要はありません。とても簡単な例を見てみましょう。

この例では、open関数を使用してmyfile.txtを開いています。 try/finallyブロックを使用すると、予期しない例外が発生した場合でも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ブロックはありません。 with文は自動的にmyfile.txtを閉じるためです。 Trueを返すprint(fp.closed)属性を呼び出して確認することもできます。

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

それでは、独自のcontext managerクラスを定義しましょう。

 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__の両方のマジックメソッドについて基本的な知識があることを願います。

48
N Randhawa

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

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ステートメントは、このメソッドの戻り値を、ステートメントのas節に指定されたターゲットがあればそれにバインドします。

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

例外が供給され、その方法がその例外を抑制したい(すなわち、それが伝播されないようにしたい)場合、それは真の値を返さなければならない。それ以外の場合、このメソッドの終了時に例外は通常どおりに処理されます。

__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は明らかにExceptionインスタンスです。

そしておそらくtracebackはPython traceback オブジェクトです。

37
Anentropic

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

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
27
Yuri Feldman

私の答えを追加してみてください(私が学んだ考え)。

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

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

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

私の悪い英語と私の不明確な説明を残念に、ありがとう....

2
Wira Bhakti