[python] Context Manager

gunny·2024년 3월 5일
0

Python

목록 보기
3/29

context Manager

  • python에서 리소스를 관리하고 사용할 때 사용
    원하는 타이밍에 정확하게 리소스를 할당하고 제공함
  • 파일, 네트워크 연결, 데이터베이스 연결 등과 같은 리소스를 사용하는 동안 리소스를 열고 닫는 작업을 보장함
    즉, 리소스를 안전하게 열고 닫는 것이 주된 사용 사례

일단 파일을 열면 file descriptor의 누수(resource leak)를 막기 위해 작업이 끝나면 닫고, 서비스나 서비스에 대한 연결 시에 임시 파일을 제거하고 닫는 작업을 해야하는데 '일반적으로 할당된 모든 리소스를 해제 해야 한다.'

  • 예외가 발생하거나 오류를 처리해야 하는 경우 모든 조합과 실행 경로에 대해 처리하는 것이 쉽지 않다.
  • 가장 많이 사용되는 컨텍스트 매니저는 with문 이다.
    아래는 예를 들어 파일을 열고 쓰는 with 문인데,
with open('file_name.txt', 'w') as file_data:
    for line in file_data:
        print(line)

결론부터 말하자면 with문을 사용해 블록 내에서 리소스가 필요한 시점에 자동으로 리소스를 할당하고 블록을 벗어날 때 리소스를 정리한다.

위 코드는 파일을 열고 데이터를 쓰고 닫는 코드이고, 데이터를 쓰는 동안 에러가 발생하면 파일을 닫기 위해 시도할 것이다.
아래와 동일한 수행을 한다.


file_data = open('file_name.txt', 'w')
try :
	file_data.write('Hello')
finally:
	file_data.close()

with문을 사용하므로서 작성한 많은 표준 코드들이 사라졌다.
with 문을 사용하는 장점은 감싸진 블록이 존재하든지 파일이 확실히 닫아진다는 것에 있다.

  • 컨텍스트 관리자 프로토콜은 파이썬에서 'with'문을 지원하기 위한 프로토콜이고, 컨텍스트 관리자를 사용하기 위해서는 두 가지 중요한 메서드가 있다.
    (1) __enter__() (2) __exit__() 이다.
  • with 문이 실행될 때 __enter__() 메서드가 호출되고, with문을 빠져나올 때 __exit__() 메서드가 호출된다.
  • 위에서 open 함수는 파일 객체를 반환하면서 동시에 컨텍스트 관리자 프로토콜을 구현한다.
    즉, 파일 객체가 with 문에 진입할 때 __enter__() 메서드 호출, with 블록 실행되고나서는 __exit__() 메서드의 호출로 파일이 자동으로 닫히게 된다.

=> python의 'with' 문으로 컨텍스트 관리자에 의해 자동으로 정리되는 자원을 안전하게 사용할 수 있다.

컨텐스트 관리자는 리소스 관리에만 사용 가능한 것이 아니라,
블록의 전후에 필요로 하는 특정 논리를 제공하기 위해 자체 컨텍스트 관리자를 구현할 수 있다.

  • 컨텍스트 관리자는 관심사를 분리하고 독립적으로 유지되어야하는 코드를 분리하는 좋은 방법이다.

context Manager 구현

(1) Class로 구현

최소한 컨텍스트 매니저는 __enter__() , __exit__() 메소드를 가지고 있다. 파일을 여는 컨텍스트 매니저 class 생성하고 사용하는 예시 코드이다.


class OpenFile(object):
	def __init__(self, file_name, method):
    	self.file_data = open(file_name, method)
    def __enter__(self):
    	return self.file_data
    def __exit__(self, type, value, trace_back):
    	self.file_data.close()
        
with OpenFile('file_name.txt', 'wb') as open_file:
	open_file.write('Hello')
    

여기서 __exit__() 는 세 가지의 인자를 전달 받고 있다.

위 코드의 흐름은

(a) with 문은 OpenFile 클래스의 __exit__() 메소드를 저장한다.
(b) OpenFile 클래스의 __exit__() 메소드를 호출한다.
(c) __enter_() 메소드는 파일을 열고 파일을 반환한다.
(d) 열려진 파일 처리는 open_file 을 통과한다.
(e) .write() 를 사용해 파일을 쓴다.
(f) with 문은 저장된 __exit__() 를 호출한다.
(g) __exit__() 문은 파일을 닫는다.

즉,

  • OpenFile 클래스의 __init__() 이 호출되어 open 함수로 파일을 읽는다.
  • __enter__() 메서드의 반환 값으로 파일 객체가 반환되고 Context에서 open_file 변수에 저장된다.
  • opened_file 객체의 write() 함수가 호출되고 'Hello'가 쓰여진다.
  • Context 내의 모든 코드가 실행되면 __exit__() 이 호출되고 파일 객체가 닫힌다. file_data.close()

이라고 보면 되겠다.

위에서 __enter__()__exit__() 에 대해 알아보자면,

(1) __enter__()

  • 이 메서드는 컨텍스트가 시작될때 실행된다.
    리소스를 할당하고 필요한 준비 작업을 수행한다.
    __enter__() 가 무언가를 반환하면 as 키워드 뒤의 변수에 그 값을 할당한다.

(2) __exit__()

  • 이 메서드는 컨텍스트가 종료될때 실행된다.
    예외 처리 및 리소스 정리와 같은 마무리 작업을 수행한다.
    __enter__() 는 예외 처리를 위한 정보를 받는데, 예외가 발생하지 않으면 세 개의 'None' 인자를 받는다.

  • 위 코드에서 __enter__() 는 메소드 인자로 type, value, trace_back 을 받고 있는데,
    (d) 열려진 파일 처리는 open_file 을 통과한다.
    (e) .write() 를 사용해 파일을 쓴다.
    (f) with 문은 저장된 __exit__() 를 호출한다.
    위의 (d) ~ (f) 사이에서 예외가 발생하면 python은 예외에서 type, value, trace_back__exit__() 에 통과시킨다.
    __exit__() 에서는 다음에 어떤 단계들이 요구되는 지에 따라 파을을 닫을 수 있는 방법을 결정한다.

예를 들어, 파일 객체가 지원되지 않는 메소드에 접근하려고 한다면

with OpenFile('file_name.txt', 'wb') as open_files:
	open_file.underfined_function('Hello!')

with문은
(a) type, value, trace_back 에러가 __exit__() 에 통과된다.
(b) __exit__() 메소드가 예외를 처리하게 한다.
(c) __exit__() 메소드가 True를 반환하면 예외가 적절하게 된 것이다.
(d) __exit__() 메소드가 True가 아닌 것을 반환하면 with 문은 예외를 발생시킨다.

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'file' objects has no attribute 'undefined_function'

__exit__() 메소드에서 예외처리를 한다면,


class OpenFile(object):
	def __init__(self, file_name, method):
    	self.file_data = open(file_name, method)
    def __enter__(self):
    	self.file_data
    def __exit__(self, type, value, trace_back):
    	print('예외 처리')
        self.file_data.close()
        return True
        
with OpenFile('file_name.txt', 'wb') as open_file:
	open_file.undefined_function()
    

위의 __exit__()가 True를 반환하면 with에서 예외는 발생하지 않지만, 위에서는 예외가 발생한 경우이므로 output으로 '예외 처리'가 출력된다.

(2) Generator로 구현

데코레이터와 제너레이터를 활용해 컨텍스트 매니저를 구현할 수 있다.
python은 이러한 목적으로 contextlib 모듈을 가지고 있다.
class 대신에 컨텍스트 매니저를 제너레이터 함수를 사용 가능하다.

  • 'contextlib' 모듈의 '@contextmanager' 데코레이터를 사용하여 함수로 간단하게 구현할 수 있다.
from contextlib import contextmanager

@contextmanager
def open_file(file_name):
	f = open(file_name, 'wb')
    yield f
    f.close()  

위의 코드로 컨텍스트 매니저를 직관적으로 구현할 수 있다.
이 메소드는 제너레이터, yield, 데코레이터에 대한 사전 지식이 필요하다.
위 코드에서는 일어날 수 있는 예외는 처리하지 못하지만, 이전의 메소드와 동일한 방법으로 동작한다.

(a) python이 yield 키워드를 만난다면, 일반적인 함수 대신에 제너레이터를 만들었기 때문이다.
(b) 데코레이션이 있어 컨텍스트 매니저는 함수 이름(open_file)을 전달 인자로서 호출한다.
(c) contextmanager 함수는 GeneratorContextManager로 감싸진 제너레이터를 반환한다.
(d) GeneratorContextManager는 open_file 함수를 할당하고, open_file 함수를 호출하는 것은 GeneratorManager를 호출하는 것이다.

위에 있는 내용을 바탕으로 아래와 같이 새롭게 만들어진 컨텍스트 매니저를 사용가능 하다.

with open_file('file_name.txt') as f:
	f.write('Hello')

참고사이트

[1] https://kimjingo.tistory.com/211
[2] https://ddanggle.gitbooks.io/interpy-kr/content/ch24-context-manager.html
[3] 2024.03.05 기준 chatgpt-3.5

profile
꿈꾸는 것도 개발처럼 깊게

0개의 댓글