Quark's Outlines: Python에서 Callable Objects 에뮬레이션
I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line exactly as you provided and preserve all formatting, markdown, and code blocks.
개요, 역사적 타임라인, 문제 및 해결책
Python에서 괄호를 사용하면 보통 함수를 호출합니다.
하지만 자신만의 객체를 함수처럼 동작하도록 만들 수도 있습니다. 클래스 안에 __call__ 메서드를 정의하면, Python은 해당 클래스의 인스턴스를 마치 함수처럼 “호출”할 수 있게 해줍니다.
Python 호출 가능 객체는 괄호와 인자를 뒤에 붙일 수 있는 모든 객체를 말합니다. 여기에는 다음이 포함됩니다:
- 함수
- 메서드
__call__()메서드를 정의한 모든 객체
Python은 __call__을 추가함으로써 사용자가 직접 객체를 호출 가능하게 만들 수 있게 합니다.
class Greeter:
def __call__(self, name):
return f"Hello, {name}!"
greet = Greeter()
print(greet("Ada"))
# prints:
# Hello, Ada!
greet가 함수가 아니지만 ("Ada")와 같이 호출하면 __call__ 메서드가 실행됩니다.
객체를 호출 가능하게 만드는 이유는 함수처럼 동작하면서도 상태를 유지하게 하고 싶을 때입니다. 다음과 같은 경우에 유용합니다:
- 이전 입력을 기억해야 할 때
- 설정값을 보관해야 할 때
- 설정 방식에 따라 동작이 달라져야 할 때
함수보다 더 많은 제어권이나 컨텍스트가 필요할 때, 호출 가능 객체가 함수를 대체할 수 있습니다.
예시: 상태를 가진 호출 가능 객체
class Counter:
def __init__(self):
self.total = 0
def __call__(self, amount):
self.total += amount
return self.total
add = Counter()
print(add(5)) # 5
print(add(10)) # 15
위 예시에서 add 객체는 함수처럼 동작하지만 내부 total 값을 기억합니다.
Python의 callable 객체 동작은 어디에서 비롯되었나요?
Python이 __call__() 메서드를 가진 모든 것을 함수처럼 취급하는 아이디어는 행동을 객체의 일부로 보는 객체‑지향 시스템에서 비롯되었습니다. 이를 통해 데이터와 로직을 하나의 객체에 결합할 수 있습니다.
| Year | Milestone |
|---|---|
| 1970s | Smalltalk의 메시지‑패싱이 함수 호출이 단순히 객체에 메시지를 보내는 것이라는 아이디어에 영감을 주었습니다. |
| 1980 | Lisp와 Scheme의 함수 객체는 클로저가 행동과 메모리를 함께 담을 수 있게 했습니다. |
| 1991 | Python은 버전 0.9.0에서 __call__()을 도입하여 __call__을 가진 객체가 함수처럼 동작하도록 만들었습니다. |
| 2001 | 내장 함수 callable()을 통해 객체가 호출 가능한지 검사하는 기능이 추가되었습니다. |
| 2025 | Callable 객체는 확장성을 위해 데코레이터와 팩토리에서 널리 사용됩니다. |
파이썬 호출 가능한 객체를 올바르게 사용하는 방법
Callable 객체는 함수처럼 동작하면서 더 많은 일을 할 수 있게 도와줍니다. 아래는 일반적인 패턴별로 문제 정의, 해결책, 예시를 정리한 내용입니다.
1. 설정을 저장하는 간단한 호출 객체
문제: 함수처럼 동작하면서 설정 데이터를 저장할 객체가 필요합니다.
해결책: __call__ 메서드를 정의하고 설정을 인스턴스 속성에 보관합니다.
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return x + self.n
add_five = Adder(5)
print(add_five(10)) # 15
2. 과거 값을 기억하는 호출 객체
문제: 호출될 때마다 사용 횟수를 추적하고 싶습니다.
해결책: 상태를 속성에 저장하고 __call__ 안에서 업데이트합니다.
class Tracker:
def __init__(self):
self.calls = 0
def __call__(self):
self.calls += 1
return self.calls
count = Tracker()
print(count()) # 1
print(count()) # 2
3. 복잡한 함수를 클래스로 교체하기
문제: 단순 함수가 점점 복잡해지고 있어, 기존 코드를 깨뜨리지 않으면서 클래스로 리팩터링하고 싶습니다.
해결책: __call__에 동일한 호출 시그니처를 구현하고, 함수가 사용되던 곳에 클래스 인스턴스를 사용합니다.
class Square:
def __call__(self, x):
return x * x
f = Square()
print(f(6)) # 36
4. 인스턴스별로 동작을 설정하기
문제: 각 인스턴스가 동일한 로직을 수행하지만 서로 다른 설정을 사용해야 합니다.
해결책: __init__에 설정 데이터를 전달하고 이를 __call__에서 활용합니다.
class Greeter:
def __init__(self, greeting):
self.greeting = greeting
def __call__(self, name):
return f"{self.greeting}, {name}!"
hi = Greeter("Hi")
hello = Greeter("Hello")
print(hi("Ada")) # Hi, Ada!
print(hello("Bob")) # Hello, Bob!
5. 객체가 호출 가능한지 안전하게 테스트하기
문제: 함수, 숫자, 기타 객체가 섞여 있을 때, 호출 가능한 객체만 호출하고 싶습니다.
해결책: 파이썬 내장 함수 callable()을 사용합니다.
def f(): return 42
class C: pass
obj = C()
print(callable(f)) # True
print(callable(obj)) # False
객체가 __call__을 정의하면 callable()은 True를 반환하고, 그렇지 않으면 False를 반환합니다.
도움이 되었나요?
아래 좋아요 버튼을 눌러 알려 주세요. 댓글로 의견도 남겨 주시면 좋겠습니다! 더 많은 콘텐츠를 원하시면 구독을 잊지 마세요. 읽어 주셔서 감사합니다!
Mike Vincent – 미국 소프트웨어 엔지니어이자 앱 개발자.
Mike Vincent에 대하여
Mike Vincent는 캘리포니아 주 로스앤젤레스 출신입니다.