본문 바로가기

진리는어디에/Python

[Python] 클래스(class) #6 callable object

이번 포스트는, 이전 #5 special method에 이어 객체를 함수 처럼 사용 할 수 있는 callable object라는 개념에 대해 살펴 보고, __call__ 스페셜 메소드를 이용해 어떻게 만드는지 알아 볼 것이다. 또한, 객체를 함수 처럼 사용 할 수 있을 때 얻을 수 있는 장점에 대해서도 살펴 보도록 한다.

이번 장은 나중에 나올 클래스 데코레이터나 기타 많은 부분에서 유용하게 사용되므로 주의 깊게 살펴 보도록 하자.

목차

  1. 파이썬 클래스 소개
  2. instance vs static
  3. dict vs slots
  4. property의 활용
  5. special method
  6. >> callable object
  7. 클래스 데코레이터(class decorator)
  8. 상속
  9. 추상 클래스와 추상 메소드

__call__ 스페셜 메소드

__call__ 스페셜 메소드에 대해 설명하기 전에 묻지도 따지지도 말고, 아래 예제를 먼저 살펴 보자. 아래 예제는 Plus라는 클래스의 객체를 함수 처럼 사용하고 있다.

class Plus :
    pass

plus = Plus()

n = plus(1, 2) # TypeError: 'Plus' object is not callable

물론 객체는 함수가 아니기 때문에 위와 같이 객체를 함수 처럼 사용하면 에러가 발생한다.

이 때 미리 약속된 스페셜 메소드 __call__을 사용하면 객체를 함수 처럼 사용 할 수 있다. 이전 장 #5 special method에서 스페셜 메소드는 특정 상황에 인터프리터에 의해 호출 되는 메소드라고 말했다. __call__ 스페셜 메소드는 객체를 함수 처럼 호출 할 때 불리는 메소드다.

class Plus :
    
    def __call__(self, x, y) :
        return x + y

plus = Plus()

n = plus(1, 2) # plus.__call__(1, 2)
print(n)       # 3

위 예제를 실행 시키면 plus 객체를 마치 함수 처럼 사용하여 3이라는 결과를 얻는다.

__call__ 스페셜 메소드는 객체를 함수 처럼 () 연산자를 사용해 호출 가능하게 한다.

callable object 장점?

앞에서 __call__ 스페셜 메소드를 이용해 객체를 일반 함수 처럼 사용하는 것을 보았다. 그러면 일반 함수를 만들면 되지 왜 굳이 객체를 함수 처럼 사용해야 하는가라는 의문이 들수 있다.

설명을 위해 한가지 상황을 가정해보자. 우리는 add라는 두 수를 더해주는 일반 함수를 가지고 있다. 그리고 이 함수가 얼마나 많이 호출 되었는지를 추적하고 싶다고 가정하자. 그럼 아래와 같이 전역 변수를 통해 함수 호출 회수를 기록 할 수 있다.

count = 0         # 함수 호출 회수를 기록할 전역 변수
def add(x, y) :
    global count
    count += 1    # 함수 호출 회수를 기록 한다
    return x + y
    
n = add(1, 2)
print(count)      # 1

하지만 위 코드에서 count와 add함수와의 연관성을 찾을 수 없다. 그럼 함수 객체에 count 변수를 저장하는 방법을 생각 해볼 수 있을 것이다.

# count = 0
def add(x, y) :
    # global count
    # count += 1 
    add.count += 1  # AttributeError: 'function' object has no attribute 'count'
    return x + y
    
n = add(1, 2)

위에서 add.count += 1은 결국 add.count = add.count + 1과 동일하다. 더하기 위해서 add.count를 먼저 읽어야 하는데 아직 해당 변수가 정의되지 않았으므로 에러가 발생한다. 이것을 해결하기 위해서는 아래와 같이 함수가 호출 되기 전에 항상 어딘가에서 add.count를 초기화 하여 먼저 변수를 생성하는 코드가 필요하다.

def add(x, y) :
    add.count += 1
    return x + y
    
add.count = 0     # add가 호출 되기 전에 초기화
n = add(1, 2)

print(add.count)

항상 add를 최초 호출하기 전에 어딘가에서 먼저 변수를 만들어 둬야 하는 것은 상당히 불편하다. 그럼 다시 처음 Plus 클래스 예제로 돌아가보자.

클래스 객체를 함수 처럼 사용 할 수 있지만, 이것은 엄연히 객체다. 객체라는 것은 내부에 자신만의 데이터를 가질 수 있는 것을 의미하고, 객체가 생성되는 시점에 변수 초기화 또한 가능하다.

class Plus :

    def __init__(self) :
        self.count = 0
        
    def __call__(self, x, y) :
        self.count += 1
        return x + y

plus = Plus()

n = plus(1, 2)

print(plus.count)

위 Plus 클래스의 객체는 add 일반 함수와 사용법은 동일하지만 "객체의 상태를 가질 수 있다"는 차이점이 있다.

callable object는 상태를 가지는 함수를 만들 수 있다.

마치며

이번 장은 __call__ 스페셜 메소드를 이용하여 callable object, 즉, 상태를 가지는 함수를 만드는 방법에 대해 알아 보았다. 다음 장 #7 클래스 데코레이터에서는 이 callable object를 이용해 이전에 배웠던 데코레이터(Decorator)를 클래스를 이용해 구현하는 방법에 대해 살펴 보도록 하겠다.

부록 1. 같이 보면 좋은 글

오늘은 여기까지

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!