새소식

인기 검색어

개발일기

Python에서 메서드 오버로딩하기

  • -

공부한 이유

Java나 Kotlin으로 코딩할 때는 메서드 오버라이딩을 자주 한다. 특히 많이 쓰는 경우는 1. 주 / 부생성자를 생성할 때 2. 테스트하기 쉽도록 인자를 쪼갤 때다

 

테스트하기 쉽도록 쪼갠다는 건 무슨 의미냐면, 예를 들어 다음과 같은 코드가 있을 수 있을 수 있다

 

void go(RandomNumberGenerator randomNumberGenerator) {
    if (randomNumberGenerator.get() > 5) {
        this.state++;
    }
}

 

위와 같은 코드에서 테스트 하고싶은 관심사는 RandomNumberGenerator.get()이 아니라, 5 이상일 때 state++하는 로직이다. 위와 같은 코드에서 인터페이스를 뽑아서 해결할 수도 있지만 나는 다음과 같은 식으로도 종종 해결한다

 

void go(RandomNumberGenerator randomNumberGenerator) {
    go(randomNumberGenerator.get())
}

void go(int number) {
    if (number > 5) {
        this.state++;
    }
}

 

이러면 주 관심사는 go(int number)에만 몰려있기 때문에 이것만 간단하게 테스트할 수 있게 된다

 

그러나 최근 python을 공부하면서, python에서는 메서드 오버로딩이나 생성자 오버로딩이 불가능하다는 것을 알게 되었다

 

그럼 python에서는 어떻게 처리할까?

 

본문

우선 python에서 오버로딩을 시도해보자. 문법에서 막지는 않고, 그냥 이전에 선언했던 내용을 덮어쓴다

 

 

만약 오버로딩이 지원된다면

 

first method called with abc

second method called with 10

 

을 출력해야 했겠지만, 둘 다 나중에 선언한 foo()를 호출한다. 여기서 의미하는 것은 함수 시그니처에서 중요한 것은 namespace만 해당하고, 인자는 이에 해당하지 않는 것으로 보인다

 

여기서 의심되는 것들은 식별자에 타입을 담지 못하는 한계 혹은 동적 언어의 특성에서 비롯된 것이 아닌가 싶다. 식별자에 타입을 담지 못하니, 시그니처에 타입을 표기할 수 없는데서 이런 방식이 유래된 것은 아닐까 하는 생각이 든다. JS에서도 비슷하게 동작했던 걸로 기억해서 동적 언어의 특성인가? 하는 생각이 들었다

 

물론, 이 점이 불편했는지 이를 해결하려는 시도들이 있었다

 

1. 조건 분기 혹은 패턴 매칭

// 출처: https://www.scaler.com/topics/function-overloading-in-python/
// 조건 분기
class areaClass:
    def area(self,a,b=None,c=None,d=None):
        
        #when a and c are passed as arguments
        if a!=None and b!=None and a!=b and a!=c:
            print("Area of the triangle",(0.5*a*b))
            
         #when a,b,c and d are passed as arguments   
        elif(b!=None and c!=None and d!=None and a==b and a==c):
             print("Area of the square",(a*c))
                
        elif(b==None and c==None and d==None):
            print("Enter more numbers")
        else: 
            if(a==c):
                print("Area of the rectangle",(a*b))
            else:
                print("Area of the rectangle",(a*c))
        
obj=areaClass()
obj.area(19,8,77)#Area of the triangle 76.0
obj.area(18,18,18,18)#Area of the square 324
obj.area(72,38,72,38)#Area of the rectangle 2736
// 출처 : https://medium.com/@restudad/deep-dive-into-python-3-10s-pattern-matching-a-game-changer-34b65437d8b0
// 패턴 매칭
def process_value(value):
  match value:
    case str(s):
      return f"String: {s}"  
    case int(i):    
      return f"Number: {i}"
    case list(items): 
      return [process_value(item) for item in items] 
    case dict(items):
      return {k: process_value(v) for k, v in items.items()}
    case _:
      raise TypeError(f"Unknown type: {type(value)}")

 

사실 가장 간단하게 활용할 수 있는 방법이다. 생성자에서도 사용할 수 있는 방법이기도 하다

 

하지만 마음에 들지 않았던 부분은 과연 이렇게 해서 OCP를 잘 지킬 수 있느냐다. 메서드 오버로딩이나 주 / 부생성자를 사용해서 분기하는 가장 큰 이유가 OCP를 잘 지키도록 하기 위함인데, 인자가 수정되면 이에 영향을 받기 때문에 코드를 수정할 일이 많아질 것 같다

 

또 불편한 점은 분기하는 조건들이 너무 지저분해지기도 하고 인자가 너무 많아져서 로직에 집중하지 못하겠다는 생각이 들었다. 오버로딩을 적극 활용하기 위해 인자들을 항상 *args나 **kargs로 받기에는 타입의 혜택을 너무 받지 못하겠다는 생각이 들었다

 

2. singledispatch

 

위와 같은 니즈가 있어서인지, singledispatch라는 걸 지원한다. 이건 python의 decorator로 구현한 건데, @functools.singledispatch로 등록해 놓으면 @함수명.register에서 타입 분기되어 처리한다

 

하지만 여기에도 아쉬운 점이 몇가지 있다

 

1. 인자 수가 같아야 한다

2. 클래스에서는 사용하지 못하는 것 같다

 

1. 인자 수를 다르게 줬을 경우

 

1의 경우 당연한 것 같다. python의 decorator는 proxy처럼 동작하기 때문에 내부에서 분기 처리해줄 수는 있어도, 외부에 드러나는 인자 수를 수정할 수는 없을 것이다

 

왜냐하면 외부로 드러나는 건 결국 _()가 아니라 foo()기 때문에 foo의 시그니처를 따라야 하기 때문이다

 

2. 클래스에서는 사용하지 못하는 것 같다

 

클래스에서 singledispatch를 선언하고 실행한 결과, _()로 분기되는 것이 아니라 fun()에서 처리됨을 볼 수 있다. 이는 클래스에서는 처리되지 않음으로 보인다. 이러면 부생성자 선언에서 사용할 수 없으니 내 관심사에서 멀어진다

 

3. multipledispatch

 

마지막으로 multipledispatch 모듈을 활용한 방법이다. 이 방법은 아직 해결하지 못했다. 한 번 시도해 보려고 하면 어떤 이유에선지 실행할 수 없다

 

 

pip에서 제대로 설치된 것을 확인했고, 인터프리터도 venv로 잡은 것을 잘 확인했는데 오류를 뱉는 이유가 궁금하다. 만약 이 기법은 클래스에서도 사용 가능하고, 인자 개수도 여럿 받을 수 있다면 잘 활용할 수 있을 것 같다

 

결론

python에서 메서드 오버로딩을 다루는 방법에 대해 조사해 보았다. 방법으로는 1. 내부에서 조건 분기 2. singledispatch 3. multipledispatch가 있다

 

그런데 한 편으로는 여기에 대해서 커다란 니즈가 없는 걸 보니 이러한 방법들이 파이써닉하다고 여겨지지는 않는 모양이다. 또한, 이 질문을 드렸을 때 다른 분께서 파이썬을 만질 때 강타입 언어들처럼 사용하는 것은 어느정도 타협 보는 것이 좋다고 말씀하셨다

 

기존 OOP / 강타입 언어들처럼 문제를 해결하지 않고, 파이써닉하면서 robust한 코드를 짜는 방법에 대해서 공부를 해보아야겠다

 

참조

https://076923.github.io/posts/Python-45/

 

Python 강좌 : 제 45강 - 디스패치

디스패치(Dispatch)

076923.github.io

- https://www.scaler.com/topics/function-overloading-in-python/

 

Function Overloading in Python - Scaler Topics

This article by Scaler Topics covers Function Overloading in Python along with various examples in order to elaborate upon the concepts of Python function overloading.

www.scaler.com

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.