들어가기 전
- 이 글은 이펙티브 파이썬을 공부하며 작성한 글입니다.
- 코드 : https://github.com/chickenchickenlove/effective-python/tree/master/item66
요약
- contextlib.contextmanager 데코레이터를 이용하면 __enter__, __exit__ 구현없이 with 절에서 사용할 수 있음.
- with 절은 with절 블록 안과 밖을 격리시키는 역할을 함. 다음 의미를 가짐.
- With절 안쪽은 특별한 Context를 가지고 실행됨을 의미함.
- With절 안 / 밖은 서로 다른 Context에서 실행됨.
- contextmanager 데코레이터를 사용한다면, try ~ finally와 yield를 함께 사용해줘야 함.
- contextmanager를 사용하면 많은 코드가 절약됨.
- try ~ finally를 반복할 필요가 없음. (lock을 쓸 때)
- 실수해서 코드 마무리 작업을 까먹지 않음.
- 재사용성이 좋음.
- With절을 이용하면 부가적인 것은 컨텍스트 매니저에서 처리하도록 하고, 개발자는 비즈니스 로직에 집중할 수 있음.
Item 66. 재사용 가능한 try/finally 동작을 원한다면 contextlib과 with문을 사용하라.
파이썬에서 with절에서 직접적으로 사용되고 싶은 경우, __enter__(), __exit__() 메서드를 구현한 클래스를 작성해야한다. 그러나 굳이 클래스를 사용하고 싶지 않은 경우가 있다. 이런 경우에는 contextlib이 제공하는 contextmanager 데코레이터를 사용하면 클래스 구현 없이 with 절을 사용할 수 있다.
@contextlib.contextmanager
def get_random_number():
try:
yield random.randint(1, 100)
finally:
print('end context')
with get_random_number() as num:
print(num)
contextmanager 데코레이터를 이용해 with 절에서 사용되려면 위와 같이 구현하면 된다.
- contextmanager 데코레이터를 명시한다.
- try ~ finally / yield로 코드를 작성한다.
- with 절로 진입했을 때, yield까지 수행된다.
- with 절이 끝날 때, finally 절이 수행된다.
이처럼 contextmanager를 이용하면 with 절에서 손쉽게 사용할 수 있는 코드를 얻게 된다. 그렇다면 with절을 이용한다는 것은 개발자들에게는 어떤 장점을 줄 수 있을까?
With절의 안/밖의 Context 분리(격리)
With 절의 내부의 코드 블록은 독립적인 Context에서 실행된다는 것을 의미할 수 있다.
import logging
import contextlib
logging.basicConfig()
@contextlib.contextmanager
def log_level(level, name):
logger = logging.getLogger(name)
old_level = logger.getEffectiveLevel()
logger.setLevel(level)
try:
yield logger
finally:
logger.setLevel(old_level)
with log_level(logging.DEBUG, 'my-log') as logger:
logger.debug(f'대상: {logger.name}!')
logger = logging.getLogger('my-log')
logger.debug('디버그 메세지는 출력되지 않습니다.')
logger.error('오류 메세지는 출력됩니다.')
위 코드가 With 절 내부가 격리된 Context에서 실행된다는 것을 보여준다.
- with절 내부에 log_level() 메서드가 호출되는 것을 볼 수 있다. log_level이 바뀌고, logger를 통해서 조절할 수 있음을 유추할 수 있음.
- with절 바깥에서는 with절 내부의 Context가 유지되지 않기 때문에 원리 logger 레벨로 원복된다.
코드만 읽을 때는 갸우뚱 할 수 있지만, 코드를 실행해보면 결과는 더 명확해진다.
DEBUG:my-log:대상: my-log!
ERROR:my-log:오류 메세지는 출력됩니다.
With절 내부에서 찍은 디버그 레벨 로깅은 잘 찍히지만, With절 바깥에서 찍은 경우에는 그렇지 않음을 알 수 있다. 코드 / 출력 결과가 모두 'With절 내부는 독립적인 Context로 동작한다'를 잘 나타낸다.
try ~ finally 및 잊기 쉬운 자원 정리 코드 단축. 비즈니스 로직만 분리
lock.acquire()
try:
# Lock 획득 후 실행할 코드.
print(1)
finally:
lock.release()
만약 내가 lock을 사용하는 경우라면 위 코드가 사용되는 부분이 많아질 것이다. 그러나 이런 코드를 사용하는 부분이 많아질수록 전체적으로 위험한 코드가 된다. 위 코드는 아래 단점들이 있기 때문이다.
- 사용자가 lock.release()를 빼먹거나 하는 치명적인 실수를 할 수도 있다.
- try ~ finally가 계속 사용될 것이기 때문에 코드에 잡음이 있다. 특히 If, For문과 함께 사용되어 Depth가 깊어질수록 그렇다.
실수하기도 쉽고, 읽기도 어려운 코드가 된다는 것이다. contextmanager는 이런 단점을 보완해준다.
# 컨텍스트 매니저를 사용하게 되면 위 단점이 모두 극복됨.
import contextlib
@contextlib.contextmanager
def get_lock():
lock.acquire()
try:
yield
finally:
lock.release()
with get_lock():
print(1)
with get_lock():
print(2)
컨텍스트 매니저를 이용해서 위와 같이 코드를 리팩토링 할 수 있다.
- try ~ finally는 한 곳에만 작성되어 있다. try ~ finally가 반복 사용되지 않도록 함.
- lock.release() 같은 로직도 with절에서 내부적으로 처리됨.
락을 획득하고 반환하는 부가적인 작업(그러나 치명적인 작업)은 With절이 내부적으로 처리하도록 한다. 그리고 개발자는 With절 블록에 비즈니스 로직만 작성하면 된다. 코드는 더 읽기 쉬워지고, 어떤 로직을 처리하는지도 좀 더 명확해진다.
'프로그래밍 언어 > 파이썬' 카테고리의 다른 글
Pytest Fixture 관련 공부 (0) | 2024.06.27 |
---|---|
Effective Python Item 40. super로 부모 클래스를 초기화 하라. (0) | 2024.02.26 |
Effective Python Item 37. 내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라. (0) | 2024.02.17 |
파이썬 3.11에 추가된 것들 살펴보기 (0) | 2024.02.17 |
Effective Python Item 41. 기능을 합성할 때는 믹스인 클래스를 사용하라 (0) | 2024.02.16 |