HOWTO · Python

Python의 메서드 오버로딩

이 자습서에서는 Python의 메서드 오버로드와 예제를 통해 그 이점을 소개합니다.

이 페이지의 내용

Python은 이름이 같은 여러 정의 중에서 인수의 개수나 타입이 일치하는 메서드나 함수를 선택하지 않습니다. 같은 이름을 다시 정의하면 나중 정의가 이전 이름 바인딩을 대체합니다. 서로 다른 호출을 지원하려면 선택적 매개변수가 있는 함수 하나를 사용하거나, 입력 타입에 따라 명시적으로 분기하거나, 제3자 다중 디스패치 패키지를 사용할 수 있습니다. 이들은 서로 다른 기법이며, 등록된 구현 중 인수 타입에 따라 선택하는 것은 마지막 방법뿐입니다.

Python은 메서드 오버로딩을 지원할까요?

Python은 기본적으로 시그니처에 따른 메서드 오버로딩을 제공하지 않습니다. 클래스에서 같은 메서드 이름을 두 번 이상 정의하면 해당 이름에는 마지막 정의만 남습니다. 이는 클래스가 __add__ 같은 특수 메서드를 정의해 연산자를 사용자 지정하는 연산자 오버로딩과 다릅니다. 선택적 인수를 받는 메서드 하나를 사용하는 경우와도 다릅니다. 이때도 구현은 하나이며, 전달받은 값에 따라 메서드가 동작을 결정합니다.

예를 들어 인사 메서드는 이름이 있으면 이름을 받고, 없으면 기본값을 사용할 수 있습니다. 이는 선택적 인수 동작이지 오버로드 두 개가 아닙니다.

선택적 매개변수에는 기본 인수 사용하기

입력이 실제로 선택 사항이고 두 호출 모두 같은 작업으로 처리할 수 있다면 기본값을 사용하세요. 아래 예제는 메서드 하나를 정의하고 이름을 전달하거나 생략해 호출합니다.

이 예제를 실행하세요:

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

class Greeter:
    def greet(self, name="there"):
        print(f"Hello, {name}!")


Greeter().greet()
Greeter().greet("Ada")

제공된 실행에서는 다음 호출을 수행했고 아래 내용이 출력되었습니다:

Hello, there!
Hello, Ada!

호출자가 name을 생략하면 기본 인수가 "there"를 제공하고, 이름을 전달하면 그 값이 사용됩니다. 리스트나 딕셔너리 같은 가변 기본값은 사용하지 마세요. 대신 None을 기본값으로 두고 메서드 안에서 새 값을 만들어야 호출 간에 하나의 가변 객체를 공유하지 않습니다.

동작을 달리하려면 타입 검사 사용하기

소수의 입력 타입에 따라 동작을 달리해야 한다면 함수 하나에서 입력을 살펴보고 명시적으로 분기할 수 있습니다. 아래 함수는 정수 두 개 또는 문자열 두 개를 받습니다. 그 밖의 조합은 Python의 + 연산자 동작에 우연히 기대는 대신, 명확한 TypeError를 발생시켜 거부합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

def combine(left, right):
    if type(left) is int and type(right) is int:
        return left + right
    if type(left) is str and type(right) is str:
        return left + right
    raise TypeError("combine expects two integers or two strings")


print(combine(7, 11))
print(combine("Hello, ", "Python"))

제공된 실행에서는 예제를 수행했고 아래 내용이 출력되었습니다:

18
Hello, Python

이는 함수 하나에서 명시적으로 타입을 검사하는 방식이지 자동 메서드 오버로딩이 아닙니다. 정확한 type(...) is ... 검사는 int의 하위 클래스인 bool 같은 하위 클래스도 제외합니다. 하위 클래스까지 허용하려면 isinstance를 사용하세요. 또한 이 함수는 정수와 문자열을 섞은 쌍을 의도적으로 허용하지 않습니다. 애플리케이션에서 그런 조합에 명확한 의미가 있을 때만 분기를 추가하세요.

타입에 따라 선택하려면 다중 디스패치 사용하기

여러 인수 타입에 따라 구현을 선택하려면 제3자 패키지인 multipledispatch를 사용해 시그니처별 구현을 등록할 수 있습니다. 프로그램을 실행할 환경에 pip install multipledispatch를 입력해 설치하세요. 다음 예제는 클래스 메서드가 아니라 함수를 정의하며, 정수 세 개를 받는 시그니처와 실수 세 개를 받는 시그니처를 각각 등록합니다:

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

try:
    from multipledispatch import dispatch
except ImportError:
    print("Install multipledispatch to run this example.")
else:
    @dispatch(int, int, int)
    def multiply(first, second, third):
        return first * second * third

    @dispatch(float, float, float)
    def multiply(first, second, third):
        return first * second * third

    print(multiply(3, 7, 8))
    print(multiply(3.6, 5.9, 9.9))

제공된 실행에서는 설치 메시지 대체 경로를 실행했고 아래 내용이 출력되었습니다:

Install multipledispatch to run this example.

의존성을 설치한 상태에서 등록된 디스패치 함수의 성공 호출은 실행하지 않았습니다. 패키지가 설치되어 있으면 등록된 시그니처와 일치하는 호출이 해당 함수 구현을 선택합니다. 여기서는 해당 시그니처만 등록했으므로 정수와 실수가 섞인 인수 조합에는 일치하는 등록이 없습니다. 프로그램에서 지원할 조합의 시그니처를 추가하고, 프로젝트에 이 의존성을 사용하는 것이 적절한지도 고려하세요.

호출 시그니처에 따라 방법 선택하기

호출자가 입력을 생략할 수 있고 구현 하나로 두 형식을 처리할 수 있다면 기본 인수를 선택하세요. 의도한 동작이 몇 가지뿐이고 함수 하나에 두어야 규칙을 쉽게 이해할 수 있다면 명시적 타입 검사를 사용하세요. 인수 타입 조합에 따른 선택이 설계의 핵심이고 추가 의존성을 받아들일 수 있다면 다중 디스패치를 사용하세요.

이 중 어떤 방법도 프로그램을 자동으로 더 빠르거나 단순하게 만들지는 않습니다. 지원하는 호출을 명확히 하세요. 기본값을 문서화하고, 허용되는 각 타입 조합의 동작을 정의하며, 디스패치 방식이 처리해야 할 모든 시그니처를 등록하세요. 어떤 패턴을 선택하든 Python의 일반적인 이름 바인딩 규칙은 같습니다.