다형성(polymorphism)은 "많은(poly) + 모양(morph)"이라는 의미로서 주로 프로그래밍 언어에서 하나의 식별자로 다양한 타입(클래스)을 처리하는 것을 의미합니다.
넓은 의미에서는 메소드 오버로딩도 다형성에 포함됩니다.
하지만 일반적으로 객체지향 프로그래밍에서 다형성이란 객체들의 타입(클래스)이 다르면 똑같은 메시지가 전달되더라고 서로 다른 동작을 하는 것을 말합니다.
다형성은 동일한 코드로 다양한 타입의 객체를 처리할 수 있는 기법입니다.

구체적인 예를 살펴봅시다. 다양한 타입의 동물들에게 speak 라는 메시지를 보낸다고 가정합시다.
만약 강아지가 speak 라는 메시지를 받는다면 "멍멍" 이라고 할 것이고 고양이가 speak 메시지를 받는다면 "야옹" 이라고 할 것입니다.
즉 똑같은 명령을 내리지만 객체의 타입이 다르면 서로 다른 결과를 얻을 수 있는 것이 다형성입니다.
여기서 중요한 것은 메시지를 보내는 측에서는 객체가 어떤 타입인지 알 필요가 없다는 점입니다. 실행 시간에 객체의 타입에 따라서 자동적으로 적합한 동작이 결정됩니다.
다형성은 어떤 경우에 사용하는 기술일까요? 다형성은 객체 지향 기법에서 하나의 코드로 다양한 타입의 객체를 처리하는 중요한 기술입니다.
예를 들어서 한 곳에 모인 동물들이 각자의 소리를 내게 하고 싶으면 어떤 동물인지 신경 쓰지 말고 무조건 speak 메시지를 보내면 됩니다.
받은 동물은 자신이 낼 수 있는 소리를 낼 것입니다. 또 사각형, 삼각형, 원과 같은 다양한 타입의 도형 객체들이 모여 있다고 합시다.
이들 도형들을 그리고 싶으면 각 객체에 draw 메시지를 보내면 됩니다. 각 도형들은 자신의 모습을 화면에 그릴 것입니다. 즉 도형의 타입을 고려할 필요가 없는 것입니다.

도형의 타입에 상관없이 도형을 그리려면 무조건 draw()를 호출하고 도형의 면적을 계산하려면 무조건 getArea()를 호출하면 됩니다.
파이썬은 다형성을 최대로 이용하고 있습니다. 예를 들어서 내장 함수 len()은 타입을 가리지 않고 작동합니다.
list = [1, 2, 3] # 리스트
print(len(list))
s = "This is a sentense" # 문자열
print(len(s))
d = {'aaa' : 1, 'bbb' : 2} # 딕셔너리
print(len(d))
<실행 결과>
3
18
2
len()은 리스트나 문자열, 딕셔너리 객체에서 동작됩니다. 파이썬은 어떻게 모든 타입에서 동작하는 함수를 구현할까요?
파이썬은 실제로는 위임(delegation)에 의해서 다형성을 구현하고 있습니다. len() 함수는 실제로 객체의 __len__()을 호출합니다.
길이를 계산하는 연산을 객체에 위임시키는 것입니다. 따라서 객체 안에 __len__() 함수만 구현되어 있으면 문제없이 동작되는 것입니다.
정수 타입과 같이 __len__() 함수가 구현되어 있지 않은 객체에 len()을 호출하면 오류가 발생합니다.
i = 10
len(i)
<실행 결과>
TypeError: object of type 'int' has no len()
결론적으로 len()이나 sort()와 같은 내장 함수들은 객체지향 언어의 요구사항인 다형성의 위임을 이용하여서 충실하게 구현하고 있다고 볼 수 있습니다.
위임(delegation)은 하나의 객체가 특정한 작업을 수행할 때, 자신이 직접하지 않고 다른 객체에게 그 작업을 위임하는 것입니다.
일반적인 의미에서의 다형성은 동일한 부모 클래스를 상속 받아서 작성된 자식 클래스들을 동일하게 처리할 수 있다는 것입니다.
또 동일한 메소드를 호출하여도 메소드는 자식 클래스에 따라 다르게 처리됩니다.
즉 앞에서 이야기하였던 고양이와 강아지에게 speak() 라는 메시지를 보내는 것을 생각해봅시다. 우리는 아래와 같이 처리하려고 합니다.
for a in animalList:
print(a.name + ':' + a.speak())
<실행 결과>
dog1 : 멍멍!
dog2 : 멍멍!
cat1 : 야옹!
이것을 위하여 우리는 Animal 이라는 클래스를 작성합시다. 이 클래스는 추상적인 동물을 나타내는 클래스입니다. Animal 클래스 안에 speak() 메소드를 구현합니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return '알 수 없음'
class Dog(Animal):
def speak(self): # 부모 클래스의 메소드를 오버라이드(재정의) 합니다.
return '멍멍!'
class Cat(Animal):
def speak(self): # 부모 클래스의 메소드를 오버라이드(재정의) 합니다.
return '야옹!'
animalList = [Dog('dog1'), Dog('dog2'), Cat('cat1')]
for a in animalList:
print(a.name + ' : ' + a.speak())
animalList에는 2마리의 강아지와 1마리의 고양이 객체가 생성됩니다. for 루프에서는 리스트에 있는 객체의 이름과 speak() 메소드를 호출합니다.
객체의 타입에 따라 서로 다른 메소드가 호출됩니다.
<실행 결과>
dog1 : 멍멍!
dog2 : 멍멍!
cat1 : 야옹!
일반적인 운송수단을 나타내는 Vehicle 클래스를 상속받아서 Car 클래스와 Truck 클래스를 작성해봅시다.

Vehicle 클래스는 다음과 같은 추상 메소드를 가지고 있습니다. 이들 추상 메소드는 자식 클래스에서 반드시 오버라이드하여야 합니다.
리스트 안에 Truck 객체 2개와 Car 객체 1개를 저장하고 각 객체에 대하여 drive()를 호출합니다.
<실행 결과>
truck1: 트럭을 운전합니다.
truck2: 트럭을 운전합니다.
car1: 승용차를 운전합니다.