파이썬에서는 일반적으로 함수를 다음과 같이 호출합니다.
def add(x, y):
return x + y
result = add(10, 20)
print(result)
그런데 파이썬에서는 객체를 함수처럼 호출하는 것도 가능합니다.
예를 들어 다음과 같은 코드입니다.
calculator = Calculator()
result = calculator(10, 20)
calculator는 함수가 아니라 클래스의 객체인데도 ()를 붙여 호출할 수 있습니다.
이 기능을 가능하게 만드는 것이 바로 __call__() 특수 메서드입니다.
이번 글에서는 __call__()이 무엇인지부터 시작해서 Callable Object를 실제로 어떻게 활용하는지 알아보겠습니다.
1. Callable Object란?
Callable은 말 그대로 호출할 수 있는 것이라는 의미입니다.
일반적인 함수는 호출할 수 있습니다.
def hello():
print("Hello")
hello()
반면 일반적인 객체는 함수처럼 호출할 수 없습니다.
class Person:
pass
person = Person()
person()
이 코드를 실행하면 호출할 수 없다는 오류가 발생합니다.
그런데 클래스에 __call__()을 정의하면 달라집니다.
class Person:
def __call__(self):
print("Person object called")
person = Person()
person()
결과:
Person object called
객체 뒤에 ()를 붙였을 때 __call__()이 실행되는 것입니다.
즉,
person()
은 개념적으로 다음과 연결됩니다.
person.__call__()
이렇게 함수처럼 호출할 수 있는 객체를 Callable Object라고 합니다.
2. 가장 간단한 __call__() 예제
먼저 가장 간단한 형태부터 살펴보겠습니다.
class Greeting:
def __call__(self):
return "Hello, Python!"
greeting = Greeting()
print(greeting())
결과:
Hello, Python!
여기서 greeting은 함수가 아닙니다.
print(type(greeting))
결과는 다음과 같은 형태입니다.
<class '__main__.Greeting'>
즉, Greeting 클래스에서 생성된 객체입니다.
그런데 __call__()이 있기 때문에 다음처럼 사용할 수 있습니다.
greeting()
3. __call__()에 인자 전달하기
__call__()도 일반 메서드처럼 매개변수를 받을 수 있습니다.
class Calculator:
def __call__(self, x, y):
return x + y
calculator = Calculator()
result = calculator(10, 20)
print(result)
결과:
30
다음 코드가 실행되면
calculator(10, 20)
파이썬은 Calculator 객체의 __call__()을 사용합니다.
개념적으로는 다음과 같습니다.
calculator.__call__(10, 20)
따라서 객체를 함수처럼 사용하면서도 클래스의 상태와 메서드를 함께 사용할 수 있습니다.
4. 함수와 Callable Object의 차이
그렇다면 그냥 함수를 사용하면 되는데 왜 객체를 함수처럼 사용하는 것일까요?
가장 큰 차이는 상태를 저장할 수 있다는 것입니다.
일반적인 함수는 호출할 때 필요한 값을 전달합니다.
def multiply(x, factor):
return x * factor
print(multiply(10, 2))
print(multiply(20, 2))
반면 Callable Object는 클래스의 속성에 값을 저장해 놓고 사용할 수 있습니다.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(10))
print(double(20))
print(double(30))
결과:
20
40
60
여기서 factor는 객체 내부에 저장되어 있습니다.
double.factor
값은 다음과 같습니다.
2
따라서 double 객체는 자신만의 상태를 가지고 있으면서 함수처럼 동작합니다.
5. 같은 클래스로 여러 Callable Object 만들기
Callable Object의 장점은 같은 클래스를 사용하면서 서로 다른 상태를 가진 객체를 만들 수 있다는 것입니다.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
triple = Multiplier(3)
ten_times = Multiplier(10)
print(double(5))
print(triple(5))
print(ten_times(5))
결과:
10
15
50
각 객체가 서로 다른 factor를 가지고 있습니다.
double → factor = 2
triple → factor = 3
ten_times → factor = 10
하지만 사용하는 방법은 모두 같습니다.
double(5)
triple(5)
ten_times(5)
이것이 Callable Object의 중요한 특징입니다.
6. 객체의 상태를 계속 유지할 수도 있다
Callable Object는 단순히 값을 저장하는 것뿐만 아니라 호출 과정에서 상태를 변경할 수도 있습니다.
예를 들어 호출 횟수를 기록하는 객체를 만들어 보겠습니다.
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
counter = Counter()
print(counter())
print(counter())
print(counter())
결과:
1
2
3
호출할 때마다 객체 내부의 count가 증가합니다.
현재 상태도 확인할 수 있습니다.
print(counter.count)
결과:
3
일반 함수로도 전역 변수나 다른 방법을 사용해서 비슷한 기능을 만들 수 있지만, Callable Object를 사용하면 상태와 동작을 하나의 객체에 묶어서 관리할 수 있습니다.
7. Callable Object인지 확인하기
파이썬에서는 어떤 객체가 호출 가능한지 callable() 함수를 사용해서 확인할 수 있습니다.
def add(x, y):
return x + y
class Calculator:
def __call__(self, x, y):
return x + y
calculator = Calculator()
print(callable(add))
print(callable(calculator))
결과:
True
True
반면 일반적인 객체는 호출할 수 없으므로 False가 나옵니다.
class Person:
pass
person = Person()
print(callable(person))
결과:
False
즉, callable()은
이 객체를
()를 사용해서 호출할 수 있는가?
를 확인하는 함수입니다.
8. Callable Object에 여러 기능을 넣을 수 있다
Callable Object의 장점은 __call__()만 사용할 수 있다는 것이 아닙니다.
일반적인 클래스처럼 다른 메서드와 속성도 함께 사용할 수 있습니다.
class Calculator:
def __init__(self, operator):
self.operator = operator
def __call__(self, x, y):
if self.operator == "add":
return x + y
if self.operator == "multiply":
return x * y
raise ValueError("지원하지 않는 연산입니다.")
def change_operator(self, operator):
self.operator = operator
사용할 수 있습니다.
calculator = Calculator("add")
print(calculator(10, 20))
calculator.change_operator("multiply")
print(calculator(10, 20))
결과:
30
200
객체를 함수처럼 사용하면서 동시에 객체의 상태를 변경하는 메서드도 사용할 수 있습니다.
9. Callable Object와 일반 함수의 차이
둘의 차이를 간단하게 정리하면 다음과 같습니다.
| 구분 | 일반 함수 | Callable Object |
|---|---|---|
| 호출 | func() | obj() |
| 상태 저장 | 외부 상태가 필요할 수 있음 | 객체 속성으로 관리 가능 |
| 메서드 추가 | 함수 내부에 제한적 | 여러 메서드 추가 가능 |
| 속성 | 함수에도 가능하지만 주 용도는 아님 | 객체 속성으로 자연스럽게 관리 |
| 클래스 활용 | 필요 없음 | 클래스 기반 |
| 복잡한 상태 관리 | 상대적으로 불편 | 편리 |
단순한 계산이나 하나의 작업만 수행한다면 일반 함수가 더 간단합니다.
반면 특정 상태를 계속 가지고 있으면서 반복적으로 호출해야 하는 경우에는 Callable Object가 유용할 수 있습니다.
10. 실전에서 사용할 수 있는 예제
Callable Object는 다양한 곳에서 활용할 수 있습니다.
예를 들어 특정 값을 기준으로 데이터를 판정하는 객체를 만들 수 있습니다.
class Threshold:
def __init__(self, threshold):
self.threshold = threshold
def __call__(self, value):
return value >= self.threshold
is_high = Threshold(80)
print(is_high(90))
print(is_high(70))
결과:
True
False
is_high는 함수처럼 보이지만 실제로는 객체입니다.
그리고 기준값도 내부에 저장되어 있습니다.
is_high.threshold
결과:
80
기준값을 변경하는 메서드를 추가할 수도 있습니다.
class Threshold:
def __init__(self, threshold):
self.threshold = threshold
def __call__(self, value):
return value >= self.threshold
def set_threshold(self, threshold):
self.threshold = threshold
이제 하나의 객체를 계속 재사용하면서 기준을 변경할 수 있습니다.
11. 데이터 처리에서도 활용할 수 있다
Callable Object는 데이터 처리처럼 동일한 작업을 여러 데이터에 반복 적용해야 하는 상황에서도 사용할 수 있습니다.
예를 들어 입력값에 일정한 값을 더하는 객체를 만들어 보겠습니다.
class Adder:
def __init__(self, value):
self.value = value
def __call__(self, x):
return x + self.value
add_ten = Adder(10)
numbers = [1, 2, 3, 4, 5]
result = [add_ten(number) for number in numbers]
print(result)
결과:
[11, 12, 13, 14, 15]
add_ten을 일반 함수처럼 사용할 수 있기 때문에 코드가 자연스럽습니다.
동시에 10이라는 상태는 객체가 관리합니다.
12. __call__()과 함수의 관계
여기서 한 가지 재미있는 점이 있습니다.
파이썬에서 함수도 실제로는 객체입니다.
def add(x, y):
return x + y
함수 역시 객체이기 때문에 호출할 수 있습니다.
print(callable(add))
결과:
True
즉, 파이썬에서는 단순히 클래스의 객체만 Callable인 것이 아닙니다.
대표적으로 다음과 같은 것들이 호출 가능합니다.
함수
메서드
클래스
__call__()을 구현한 객체
예를 들어 클래스 자체도 호출할 수 있습니다.
class Person:
pass
person = Person()
여기서
Person()
을 호출하면 새로운 Person 객체가 생성됩니다.
따라서 파이썬에서 Callable이라는 개념은 상당히 중요한 개념입니다.
13. 언제 Callable Object를 사용하면 좋을까?
모든 함수에 __call__()을 사용할 필요는 없습니다.
오히려 간단한 작업이라면 일반 함수가 더 읽기 쉽습니다.
예를 들어 다음 정도의 기능이라면 일반 함수가 충분합니다.
def add(x, y):
return x + y
하지만 다음과 같은 상황이라면 Callable Object를 고려할 수 있습니다.
상태를 계속 유지해야 하는 경우
class Counter:
...
특정 설정을 저장한 채 반복 호출해야 하는 경우
class Multiplier:
...
객체의 상태와 호출 동작을 함께 관리해야 하는 경우
class Threshold:
...
동일한 인터페이스로 여러 객체를 사용해야 하는 경우
각 객체가 서로 다른 설정을 가지고 있지만 모두 다음과 같은 형태로 호출할 수 있습니다.
processor(data)
이런 구조는 데이터 처리나 머신러닝 코드에서도 자주 볼 수 있습니다.
14. Callable Object를 무조건 사용하면 안 되는 이유
Callable Object가 편리하다고 해서 모든 것을 __call__()로 만드는 것은 좋지 않습니다.
예를 들어 단순한 계산 하나를 수행하기 위해 굳이 클래스를 만들면 코드가 오히려 복잡해질 수 있습니다.
def square(x):
return x ** 2
이 정도 기능이라면 일반 함수가 훨씬 간단합니다.
반면 다음처럼 상태가 필요하다면 클래스가 더 적합할 수 있습니다.
class Power:
def __init__(self, exponent):
self.exponent = exponent
def __call__(self, x):
return x ** self.exponent
사용:
square = Power(2)
cube = Power(3)
print(square(5))
print(cube(5))
결과:
25
125
따라서 중요한 것은 __call__()을 사용할 수 있느냐가 아니라 정말 객체의 상태와 동작을 하나로 묶어야 하는 상황인지입니다.
마무리
이번 글에서는 Callable Object와 __call__()에 대해 알아봤습니다.
핵심은 간단합니다.
class Calculator:
def __call__(self, x, y):
return x + y
이렇게 __call__()을 정의하면 객체를 함수처럼 사용할 수 있습니다.
calculator = Calculator()
print(calculator(10, 20))
그리고 Callable Object는 단순히 함수처럼 호출되는 것뿐만 아니라 객체이기 때문에 내부에 상태와 다른 메서드를 함께 가지고 있을 수 있습니다.
정리하면 다음과 같습니다.
일반 함수
↓
func()
Callable Object
↓
obj()
↓
obj.__call__()
특히 상태를 저장하면서 반복적으로 호출해야 하는 작업에서 Callable Object가 유용합니다.
다음 글에서는 여기서 한 단계 더 나아가 클래스 데코레이터(Class Decorator)를 살펴보겠습니다.
함수 데코레이터에서는 함수를 감싸거나 변경했다면, 클래스 데코레이터에서는 클래스 자체를 대상으로 어떤 작업을 할 수 있는지 알아보겠습니다.