ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 싱글-디스패치 함수(single-dispatch function)
    Python/Python 2024. 7. 3. 16:59
    반응형

    객체지향 프로그래밍에서 JAVA나 C++ (다른랭기지도 있겠지만 생략) 등 에서 같은 함수를 2개 이상 작성하여 매개변수에 따라서 함수 처리를 다르게 하는 것을 본 적이 있을것이다. 그것을 오버로딩이라고 한다.

    파이썬은 오버로딩을 지원하지 않는다.

    결론부터 말하자면 오버로딩은 지원하지 않는다. 파이썬은 연산자 오버로딩(던더메서드를 활용한 add, sub 등등) 은 제공하지만 함수와 메서드 오버로딩은 지원하지 않는다. 한 모듈 안에서 같은 이름을 사용하면 맨 마지막으로 정의된 매서드가 앞에 모든 정의를 덮어 씌워버린다.

    그렇다면 오버로딩을 못하나?

    파이썬은 쉬운 언어이므로 다른 방법으로 메서드 오버로딩과 같은 개념으로 지원한다. 

    • 메서드/서브클래싱을 이용: 함수가 매개변수를 구분하도록 해당 타입의 메서드로 정의함으로써 특정한 타입과 연결 할 수 있슴.
    • 인수와 키워드 인수 언패킹 이용: 파이썬은 가변함수 *args, **kwars 패턴을 통해 여러 인수를 함수 시그니처나 사용할 수 있는 기능을 제공함
    • 타입 체킹 이용: isinstance 메서드를 이용하여 입력 인수를 특정 타입 및 클래스와 비교 한뒤 처리 방법을 결정 할 수 있슴

    예제 isinstance를 활용한 

    class Calculator:
        def add(self, a, b):
            if isinstance(a, int) and isinstance(b, int):
                return a + b
            elif isinstance(a, float) and isinstance(b, float):
                return a + b
            else:
                raise TypeError("Unsupported operand types")
    
    # Example usage
    calc = Calculator()
    print(calc.add(2, 3))  # Output: 5
    print(calc.add(2.5, 3.7))  # Output: 6.2

    파이썬은 자동적으로 float과 int에 연산을 지원하긴 하지만 그냥 예제용으로 느낌만 보면 
    인자가 정수형으로 올 때 와 실수형으로 올때 조건을 다르게 가진다 이것으로 다른 언어에서 오버로딩을 구현할 수 있고 같은 개념이라고 보면된다.

    다만 이러한 방법에 한계는 존재한다. 이 기법은 함수 호출 시그니처의 수가 적을 떄 는 매우 편리하게 이용할 수 있지만 지원하는 타입의 수가 많아지면 보다 모듈화된 패턴을 사용하는 것이 좋다. 이런 패턴들은 싱글-디스패치 함수에 의존한다.

    이것에 대한 해결 방법은?

    함수 오버로딩의 대한이 필요하고 대안 함수를 많이 구연해야 하는 경우라면 if instance 사용은 선택지에서 배제해도 된다 왜냐하면 입력 인수를 다르게 처리하기 위해 여러 갈래로 나뉘는 큰 함수를 만드는 것은 썩 좋은 디자인은 아니다.

    파이썬 표준 라이브러리에서는 보다 편리한 대안을 제공한다 functools.singledispatch()  데커레이터를 이용하면 한 함수의 여러 구현을 등록 할 수 있다 이것은 인수를 수의 제한 없이 받을 수 있지만 구현은 첫 번쨰 인수의 타입에 선정(dispatch) 된다.

    from numbers import Real
    from datetime import datetime
    from functools import singledispatch
    
    
    @singledispatch
    def report(value):
        return f"raw: {value}"
    
    
    @report.register
    def _(value: datetime):
        return f"dt: {value.isoformat()}"
    
    
    @report.register
    def _(value: Real):
        return f"real: {value:f}"
    
    
    @report.register
    def _(value: complex):
        return f"complex: {value.real}{value.imag:+}j"
    
    
    print(report(datetime.now()))
    print(report(100 - 30j))
    print(report("January"))
    print(report(9001))

    위 코드는 single-dispatch 기법을 적용하여 파이썬에서 오버로딩 한 경우다.

    _ 토큰을  함수 이름으로 사용한 점에 주의한다. 여기에는 두가지 목적이 있다. 첫 번째, 이는 명시적으로 사용되지 않아야 하는 객체의 이름에 관습적으로 사용한다. 두 번째 report라는 이름을 이용하면 원래 함수를 숨기게 되므로 그 함수에 접근해 새로운 타입을 정의할 수 없게된다.

    report()함수는 등록된 함수 컬렉션의 진입점(entrypoint)이 된다.  인수를 전달해서 호출할 때마다 report.registry에 저장된 등록 매핑을 검색한다. 언제나 최소 하나의 키가 객체 타입을 함수의 기본 구현에 매핑한다.

     

    클래스 안에서도 사용하는 방법을 아라보자

    functiools.singledispatch() 메서드를 그대로 받으면 항상 첫 번째 인수는 self이므로 해당 방법은 적합하지 않는다.

    친절하게 functools에서 singledispatchmethod() 라는 함수를 지원해준다.

    class Exmaple:
        
        @singledispatchmethod
        def method(self, argument):
            pass
    
        @method.register
        def _(self, argument: float):
            pass

    일단 파이썬에서는 오버로딩과 유사한 다형성 같지는 않다는 점에 주의해야한다. 또한 여러 인수에 타입에 대한 하나의 함수의 여러구현을 제공할 수 없으며 파이썬 표준라이브러리는 현재 이러한 다중-디스패치 유틸리티를 제공하지 않는다,.

    글을 작성하던 와 중에 pypi multipledispatch 가 존재하긴 하는 것 같긴한데 관리를 안하는 것 같아 과감하게 패스했다.

     

    반응형

    댓글

Designed by Tistory.