파이썬의 '__enter__'및 '__exit__'설명


362

나는 누군가의 코드에서 이것을 보았다. 무슨 뜻이에요?

    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이 4 예 def __enter__(self)PEP 343는 아무도하지 않습니다 return self: python.org/dev/peps/pep-0343은 . 왜 그렇게 생각하십니까?
슬픔

4
@Grief : 2 개 이유로, 내 의견으로는, 1) 나는 다른 방법을 호출 할 수 없습니다 self여기에 설명 된대로 객체를 : stackoverflow.com/questions/38281853/... 2) self.XYZ는 자기 개체의 한 부분과 유지 관리 관점에서 나에게 부적절한 것으로 보이는 핸들 만 반환합니다. 차라리 완전한 개체에 대한 핸들을 반환하고 그 구성 요소에만 공개 API를 제공하는 것을 선호 self내가 에서처럼 사용자에게 노출 할, 객체 with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
파일 객체는 self에서 파일을 반환 __enter__하므로 파일을 f내부 처럼 처리 할 수 ​​있습니다.with open(...) as f
holdenweb

2
이해해야 할 미묘한 점 중 하나는 객체가 초기화하는 데 매개 변수가 필요한 경우 self가 아니라 init 이어야합니다 .
dfrankow

70

컨텍스트 관리자 가 무엇인지 아는 경우 더 이상 마술 __enter____exit__방법 을 이해할 필요가 없습니다 . 아주 간단한 예를 보자.

이 예제에서는 open 함수를 사용 하여 myfile.txt엽니 다 . 시도는 / 마지막으로 예기치 않은 예외가 발생하더라도 보장하지만 차단 MYFILE.TXT가 폐쇄됩니다.

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

이제 문을 사용 하여 동일한 파일을 엽니 다 .

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

코드를 보면 파일을 닫지 않았으며 시도 / 결국 차단 이 없습니다 . 때문에 함께 문이 자동으로 닫힙니다 MYFILE.TXT를 . print(fp.closed)attribute-를 반환 하여 확인할 수도 있습니다 True.

파일에 의해 반환 (내 예제에서 FP) 오브젝트 때문이다 개방 이 내장 된 방법 기능 __enter____exit__. 컨텍스트 관리자라고도합니다. with 블록 __enter__의 시작 부분 __exit__ 메소드가 호출되고 마지막에 메소드가 호출됩니다. 참고 : 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____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)
이 객체와 관련된 런타임 컨텍스트를 종료하십시오. 매개 변수는 컨텍스트를 종료시킨 예외를 설명합니다. 컨텍스트가 예외없이 종료 된 경우 세 인수는 모두 None입니다.

예외가 제공되고 메소드가 예외를 억제 (예 : 전파되지 않도록)하려는 경우 true 값을 리턴해야합니다. 그렇지 않으면이 메소드를 종료하면 예외가 정상적으로 처리됩니다.

참고 __exit__()방법은 리 레이즈 안 전달 된 예외; 이것이 발신자의 책임입니다.

__exit__메소드 인수 에 대한 명확한 설명을 원했습니다 . 이것은 부족하지만 우리는 그들을 추론 할 수 있습니다 ...

아마도 exc_type예외의 클래스 일 것입니다.

전달 된 예외를 다시 발생시키지 말아야합니다. 이것은 우리에게 인수 중 하나가 실제 예외 인스턴스 일 수 있음을 제안합니다 ... 또는 아마도 유형과 값에서 직접 인스턴스화해야합니까?

우리는이 기사를보고 대답 할 수 있습니다 :
http://effbot.org/zone/python-with-statement.htm

예를 들어 다음 __exit__메소드는 모든 TypeError를 삼키지 만 다른 모든 예외는 통과시킵니다.

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

... value예외 인스턴스입니다.

그리고 아마도 traceback파이썬 역 추적 객체 일 것입니다.


2
동의하다. 이 URL은 찾기가 너무 어렵습니다.
Shihao Xu 2

다음 ARG 사용을 지적 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 statement "( 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

이것을 컨텍스트 관리자라고하며 다른 프로그래밍 언어에 대해 비슷한 접근법이 존재한다고 덧붙이고 싶습니다. 그것들을 비교하면 파이썬의 컨텍스트 관리자를 이해하는 데 도움이 될 수 있습니다. 기본적으로 컨텍스트 관리자는 초기화해야하는 일부 리소스 (파일, 네트워크, 데이터베이스)를 처리 할 때 사용되며 어느 시점에서 해제 (처리)됩니다. Java 7 이상 에는 다음 과 같은 형식의 자동 자원 관리 기능이 있습니다.

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

Session AutoClosable은 (다수의) 하위 인터페이스 중 하나 를 구현해야합니다 .

C # 에서는 다음 과 같은 형식의 리소스를 관리하기위한 명령문을 사용합니다.

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

어느 Session것을 구현해야합니까 IDisposable?

파이썬 , 우리가 사용하는 클래스를 구현해야 __enter__하고 __exit__. 따라서 다음과 같은 형태를 취합니다.

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

그리고 다른 사람들이 지적했듯이 모든 언어에서 try / finally 문을 사용하여 동일한 메커니즘을 구현할 수 있습니다. 이것은 단지 구문 설탕입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.