파일 입출력때 스마트 포인터같이 작동하겠거니 추측하며 Context Manager인 with-as 구문을 사용해 왔다. 그런데 이게 네트워크 커넥션에도 사용되는걸 보고 정확한 동작을 알고 싶어서 그 배후의 동작을 real python을 통해 공부했다. 동작 자체는 예상한 것과 크게 다르지 않지만, 정확히 어떤건지 적어본다.
일단 사용방법은 파일 입출력의 예를 들면 Context Manager를 안쓸 때 다음과 같이 익숙한 코드형태지만,
f = open("hello.txt", "w")
try:
f.write("hello, world")
finally:
f.close()
Context Manger를 사용하면 ‘with’ 키워드를 사용하여 다음과 같이 간편하게 쓸 수 있다.
with open("hello.txt", "w") as f:
f.write("Hello world!")
with 블럭을 빠져나갈 때, file.close()
를 알아서 해줘서 실수로 파일을 닫지 않는 일이 없도록 해준다. 이것은 동기화 오브젝트인 lock을 사용할 때나, 네트워크 커넥션에도 마찬가지로 동작한다. ‘as’ 키워드는 python에서 alias를 의미하며, 다른 곳에서도 사용한다. 예를 들면 다음과 같다.
import Calendar as c
...
c.month
...
동작 원리를 알면, 그 인터페이스를 이용해서 Context Manager로 동작하는 커스텀 객체를 만들 수 있다. 클래스의 예를들면 다음과 같다.
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, "w")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
with ManagedFile("Hello.txt") as f:
f.write("Hello world!")
with 구문에서 __enter__ 멤버함수가 불리고, 블럭을 나갈 때, __exit__ 멤버함수가 불리는 것. 의외로 간단하다. 함수에 대해서도 쓸 수 있는데, contextmanager 데코레이터가 사용된다.
from contextlib import contextmanager
@contextmanager
def managed_file(name):
file = None
try:
file = open(name, "w")
yield file
finally:
if file is not None:
file.close()
....
with managed_file("Hello.txt") as f:
f.write("Hello world!")
중간에 있는 yield 부분을 눈여겨 봐야하는데, C#의 coroutine과 같다면, with문에서 여기까지 실행되고 file 핸들러를 돌려준다. 그리고 with 블럭이 끝날 때, 여기서부터 다시 실행되는 것.
그냥 그러려니 했는데, 알고나면 실제 구현도 간단하고 yield 문 사용이 뭔가 스마트한 느낌이 드네.