Python 모듈을 호출 가능하게 만들기: Cadule 소개
Source: Dev.to

Python 코드를 작성할 때, Node.js에서 제공하는 기능이 그리워지는 경우가 종종 있습니다. 바로 모듈을 require 하면 바로 호출 가능한 형태가 되는 기능이죠. Node.js에서는 모듈에서 함수를 내보내고 바로 호출할 수 있습니다:
const messUpThings = require('./mess-up-things');
messUpThings(); // Works!
이 패턴을 좋아하지 않는 개발자도 있겠지만, 합리적인 사용 사례가 존재합니다.
The Problem
mess_up_things처럼 스스로를 설명하는 이름을 가진 헬퍼 함수를 작성해야 하는 상황을 생각해 보세요. 함수 이름 자체가 무엇을 하는지 알려줍니다. 선택지는 다음과 같습니다:
- util 혹은 helper 모듈을 만든다:
util.py혹은helper.py에 넣는다. 많은 개발자, 특히 Go 개발자들은 이런 일반적인 이름을 꺼려하고 더 설명적인 패키지 이름을 선호합니다. - 전용 파일을 만든다:
mess_up_things.py에 넣는다. 그러면 다음과 같이 다소 장황한 import 문을 써야 합니다:
from mess_up_things import mess_up_things
이런 반복은 불필요하게 느껴집니다. 다음과 같이 할 수 있다면 얼마나 좋을까요?
import mess_up_things
mess_up_things()
Exploring Solutions
Python의 매직 메서드에 익숙하다면, __call__ 메서드를 가진 객체는 호출 가능해진다는 것을 알 수 있습니다. 모듈은 types.ModuleType의 인스턴스이며 __getattr__ 같은 매직 메서드를 지원하므로, 모듈에 직접 __call__ 메서드를 정의할 수 있지 않을까 생각해 볼 수 있습니다.
안타깝게도 Python은 기본적으로 이를 지원하지 않습니다. 실제로 모듈 수준의 __call__ 지원을 제안한 PEP 713이 있었지만, 거절되었습니다.
하지만 다음과 같은 트릭을 이용하면 원하는 동작을 구현할 수 있습니다:
import sys
class MyModule(sys.modules[__name__].__class__):
def __call__(self):
print("Messing up things...")
sys.modules[__name__].__class__ = MyModule
이 코드는 런타임에 모듈의 __class__를 동적으로 교체함으로써 동작합니다. 하지만 매번 이 보일러플레이트 코드를 복사‑붙여넣고 싶지는 않겠죠?
Enter Cadule
이 보일러플레이트를 매번 작성하지 않도록, 저는 Cadule(Callable [Mo]dule Less의 약자)이라는 패키지에 메커니즘을 캡슐화했습니다.
Installation
pip install cadule
Usage
mess_up_things.py 파일을 다음과 같이 작성합니다:
import cadule
@cadule
def __call__():
print("Messing up things...")
그런 다음 Python REPL이나 다른 스크립트에서:
>>> import mess_up_things
>>> mess_up_things()
Messing up things...
>>> callable(mess_up_things)
True
인자를 전달하거나 반환값을 줄 수도 있습니다:
import cadule
@cadule
def __call__(target):
return f"Messing up {target}!"
>>> import mess_up_things
>>> mess_up_things("the database")
'Messing up the database!'
When to Use It
Cadule은 특히 다음과 같은 경우에 유용합니다:
- 단일 목적 모듈: 모듈의 주요 목적이 하나의 함수를 노출하는 경우.
- DSL 및 유창한 인터페이스: 보다 자연스러운 API를 만들고 싶을 때.
- 스크립트와 유틸리티: 커맨드‑라인 도구를 직관적으로 사용할 수 있게 할 때.
GitHub: