HOWTO · Python

Python의 과부하 연산자

이 튜토리얼은 연산자 오버로딩의 개념, 유형 및 파이썬에서의 코드 구현을 보여줍니다.

이 페이지의 내용

연산자 오버로딩을 사용하면 사용자 정의 Python 타입의 객체에 익숙한 연산자가 어떤 의미를 갖는지 정의할 수 있습니다. 예를 들어 +는 숫자를 더하고 문자열을 이어 붙입니다. 같은 연산 식이 피연산자의 타입에 따라 다르게 동작하므로, 연산자 오버로딩은 타입이 다형성을 지원하는 한 방법입니다. 다형성에 관한 더 넓은 설명은 다형성을 참고하세요. (연산자 오버로딩; 특수 메서드)

Python은 연산자를 특수 메서드에 어떻게 전달하는가

Python은 연산 식을 특수 메서드에 연결합니다. 메서드 이름이 밑줄 두 개로 시작하고 끝나기 때문에 던더 메서드라고도 합니다. left + right 같은 이항 식에서는 피연산자 타입에 따라 일반 __add__() 메서드와 반사 __radd__() 메서드의 실행 여부와 순서가 결정됩니다. 오른쪽 피연산자 타입이 왼쪽 피연산자 타입의 엄격한 하위 클래스이고 반사 메서드를 재정의했다면, Python은 왼쪽의 일반 메서드보다 오른쪽의 반사 메서드를 먼저 시도할 수 있습니다. 이와 별개로 왼쪽 메서드가 싱글턴 NotImplemented을 반환하면, 해당되는 경우 Python은 오른쪽 피연산자의 반사 메서드를 이어서 시도할 수 있습니다. 어느 쪽도 연산을 지원하지 않으면 식은 TypeError로 실패합니다.

피연산자 타입이 지원되지 않을 때는 NotImplemented을 반환하세요. 이를 발생시키거나 문자열 "NotImplemented"을 반환하지 마세요. 그래야 Python이 디스패치를 계속할 수 있습니다. 다른 이항 연산자도 각 연산에 대응하는 특수 메서드를 통해 비슷한 방식으로 처리됩니다.

add()로 덧셈 구현하기

유용한 덧셈 메서드는 다른 피연산자의 타입을 확인한 다음, 같은 사용자 정의 타입의 새 값을 반환합니다. 다음 예제는 두 Score 객체의 덧셈을 지원하고 다른 타입은 처리하지 않음을 알립니다. 그러면 Python이 반사 디스패치를 시도할 수 있습니다.

이 예제를 실행해 보세요.

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

class Score:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        if not isinstance(other, Score):
            return NotImplemented
        return Score(self.value + other.value)


result = Score(2) + Score(3)
print(result.value)
5

덧셈 결과는 5입니다. 서로 다른 타입을 더할 때는 다른 타입에 __radd__()을 구현해 오른쪽에 있는 Score를 처리하게 할 수 있습니다. 타입 간 덧셈에 분명한 의미가 있을 때만 두 메서드를 모두 구현하세요.

gt()로 대소 관계 구현하기

해당 타입에서 left > right가 무엇을 뜻하는지 __gt__()로 정의하세요. 비교 메서드는 보통 문장이나 그 밖의 참으로 평가되는 객체가 아니라 불리언 값 True 또는 False를 반환해야 합니다. 다음 예제는 객체의 숫자 값을 비교합니다.

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

class Measurement:
    def __init__(self, value):
        self.value = value

    def __gt__(self, other):
        if not isinstance(other, Measurement):
            return NotImplemented
        return self.value > other.value


print(Measurement(5) > Measurement(3))
True

비교 결과는 True입니다. 지원하지 않는 피연산자에 NotImplemented을 반환하면, 서로 다른 값이 우연히 비교 가능한 것으로 취급되는 대신 Python이 비교 디스패치 규칙을 따르도록 할 수 있습니다.

eq()로 동등성 정의하기

두 인스턴스가 같다는 것이 무엇을 의미하는지 __eq__()로 정의하세요. 값 객체라면 정체성을 결정하는 필드를 비교하고 불리언 값을 반환합니다. "Equal"을 반환하는 것은 같은 의미가 아닙니다. 비어 있지 않은 문자열은 참으로 평가되므로, 불리언 결과가 아니더라도 동등성 검사가 참인 것처럼 보이게 할 수 있습니다.

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

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y


print(Point(2, 3) == Point(2, 3))
True

동등성 비교 결과는 True입니다. 호출자가 객체를 이해하는 방식에 맞춰 비교할 필드를 선택하세요. 동등성을 정의하면 해시 가능성에도 영향을 줍니다. 동등성에 관련된 상태가 안정적으로 유지되지 않는 가변 값 객체는 해시 가능하게 만들지 않는 편이 좋습니다.

연산자의 한계와 실용적인 설계

연산자 오버로딩은 타입에 따라 연산자의 동작을 바꾸지만, 연산자의 형태까지 바꾸지는 않습니다. ~value 같은 단항 연산자는 피연산자가 하나이고 left + right 같은 이항 연산자는 피연산자가 둘입니다. 특수 메서드로 한 형태를 다른 형태로 바꿀 수는 없습니다.

각 오버로드가 해당 타입에서 직관적으로 쓰이도록 하고, 다음 식에서 호출자가 사용할 수 있는 값을 반환하세요. Python의 디스패치가 계속될 수 있다면 지원하지 않는 피연산자 타입에 NotImplemented을 반환하세요. 익숙한 기호에 관계없는 의미를 부여하기보다 각 연산에 대응하는 메서드를 사용하세요. 이렇게 하면 사용자 정의 객체가 내장 타입과 함께 예측 가능하게 동작합니다.