포스트

[Python 100일 챌린지] Day 38 - 다형성(Polymorphism)

[Python 100일 챌린지] Day 38 - 다형성(Polymorphism)

강아지, 고양이, 새를 리스트에 넣고 각자 소리 내게 하려면 타입마다 다른 코드를 작성해야 할까요? 🤔 ──아니요! 모두 speak() 메서드만 있다면 같은 방식으로 호출할 수 있습니다! 😊

리모컨을 생각해보세요. TV, 에어컨, 선풍기 모두 “전원” 버튼이 있습니다. 리모컨마다 작동 원리는 다르지만, 우리는 그냥 “전원” 버튼만 누르면 되죠. 각 기기가 어떻게 켜지는지 신경 쓸 필요 없습니다!

게임 캐릭터도 마찬가지입니다. 전사, 마법사, 궁수 모두 attack() 메서드가 있으면, for character in characters: character.attack() 이렇게 간단하게 처리됩니다.

이것이 다형성(Polymorphism)입니다! 하나의 인터페이스로 다양한 타입을 다루는 OOP의 꽃! 💡

🎯 오늘의 학습 목표

⭐⭐⭐⭐ (35-45분 완독)

📚 사전 지식


🎯 학습 목표 1: 다형성의 개념 이해하기

다형성(Polymorphism)은 “여러 형태”를 의미하며, 같은 인터페이스로 다양한 타입을 다루는 능력입니다.

🎸 실생활 비유: 악기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Guitar:
    def play(self):
        return "통통통♪"

class Piano:
    def play(self):
        return "딩동댕♬"

class Drum:
    def play(self):
        return "쿵짝쿵짝♩"

# 다형성 - 같은 메서드명, 다른 동작
instruments = [Guitar(), Piano(), Drum()]

for instrument in instruments:
    print(instrument.play())  # 각자 다른 소리!

실행 결과:

1
2
3
통통통♪
딩동댕♬
쿵짝쿵짝♩

🎯 학습 목표 2: 덕 타이핑 이해하기 (Duck Typing)

“오리처럼 걷고, 오리처럼 꽥꽥거리면, 그것은 오리다”

Python은 타입보다 동작을 중요하게 여깁니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dog:
    def speak(self):
        return "멍멍!"

class Cat:
    def speak(self):
        return "야옹~"

class Robot:
    def speak(self):
        return "삐빕삐빕"

def make_sound(obj):
    """객체의 타입에 관계없이 speak() 호출"""
    return obj.speak()

# 모두 speak()만 있으면 OK!
print(make_sound(Dog()))    # 멍멍!
print(make_sound(Cat()))    # 야옹~
print(make_sound(Robot()))  # 삐빕삐빕

isinstance() 체크 없이 작동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FileWriter:
    def write(self, data):
        with open('file.txt', 'w') as f:
            f.write(data)

class DatabaseWriter:
    def write(self, data):
        # DB에 저장
        print(f"DB에 저장: {data}")

class LogWriter:
    def write(self, data):
        # 로그 기록
        print(f"로그: {data}")

def save_data(writer, data):
    """writer 타입 체크 없이 write() 호출"""
    writer.write(data)

# 모든 writer는 write() 메서드만 있으면 됨
save_data(DatabaseWriter(), "User Data")
save_data(LogWriter(), "Error occurred")

🎯 학습 목표 3: 연산자 오버로딩 익히기

특수 메서드로 연산자 동작을 정의할 수 있습니다!

산술 연산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        """+ 연산자"""
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        """- 연산자"""
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        """* 연산자 (스칼라 곱)"""
        return Vector(self.x * scalar, self.y * scalar)

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# 사용
v1 = Vector(3, 4)
v2 = Vector(1, 2)

v3 = v1 + v2  # __add__ 호출
print(v3)     # Vector(4, 6)

v4 = v1 - v2  # __sub__ 호출
print(v4)     # Vector(2, 2)

v5 = v1 * 2   # __mul__ 호출
print(v5)     # Vector(6, 8)

비교 연산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Money:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        """== 연산자"""
        return self.amount == other.amount

    def __lt__(self, other):
        """< 연산자"""
        return self.amount < other.amount

    def __le__(self, other):
        """<= 연산자"""
        return self.amount <= other.amount

    def __gt__(self, other):
        """> 연산자"""
        return self.amount > other.amount

    def __ge__(self, other):
        """>= 연산자"""
        return self.amount >= other.amount

    def __str__(self):
        return f"{self.amount:,}"

m1 = Money(10000)
m2 = Money(5000)

print(m1 > m2)   # True
print(m1 == m2)  # False
print(m2 <= m1)  # True

컨테이너 연산자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []

    def __len__(self):
        """len() 함수"""
        return len(self.songs)

    def __getitem__(self, index):
        """[] 인덱싱"""
        return self.songs[index]

    def __setitem__(self, index, value):
        """[] 할당"""
        self.songs[index] = value

    def __contains__(self, item):
        """in 연산자"""
        return item in self.songs

    def add_song(self, song):
        self.songs.append(song)

# 사용
playlist = Playlist("내가 좋아하는 노래")
playlist.add_song("Yesterday")
playlist.add_song("Imagine")
playlist.add_song("Bohemian Rhapsody")

print(len(playlist))              # 3
print(playlist[0])                # Yesterday
print("Imagine" in playlist)      # True

for song in playlist:             # __getitem__으로 반복 가능
    print(f"{song}")

🎯 학습 목표 4: 추상 베이스 클래스 활용하기 (ABC)

덕 타이핑은 유연하지만, 팀 프로젝트에서는 “반드시 구현해야 하는 메서드”를 명시하고 싶을 때가 있습니다.

추상 베이스 클래스(ABC)는 “이 메서드는 꼭 구현하세요!”라고 강제하는 방법입니다. 구현하지 않으면 인스턴스 생성 자체가 안 됩니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from abc import ABC, abstractmethod

class Shape(ABC):
    """도형 추상 클래스"""

    @abstractmethod
    def area(self):
        """넓이 계산 (구현 필수)"""
        pass

    @abstractmethod
    def perimeter(self):
        """둘레 계산 (구현 필수)"""
        pass

    def describe(self):
        """일반 메서드 (선택)"""
        return f"넓이: {self.area():.2f}, 둘레: {self.perimeter():.2f}"

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius

# 사용
shapes = [
    Rectangle(5, 3),
    Circle(4)
]

for shape in shapes:
    print(shape.describe())

# ❌ 추상 클래스는 인스턴스화 불가
# shape = Shape()  # TypeError!

🎯 학습 목표 5: 실전 예제로 종합 정리하기

지금까지 배운 다형성, 덕 타이핑, ABC를 모두 활용한 결제 시스템을 만들어봅시다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from abc import ABC, abstractmethod
from datetime import datetime

class PaymentMethod(ABC):
    """결제 수단 추상 클래스"""

    @abstractmethod
    def pay(self, amount):
        """결제 처리 (추상 메서드)"""
        pass

    @abstractmethod
    def refund(self, amount):
        """환불 처리 (추상 메서드)"""
        pass

class CreditCard(PaymentMethod):
    """신용카드 결제"""

    def __init__(self, card_number, owner):
        self.card_number = card_number
        self.owner = owner

    def pay(self, amount):
        print(f"💳 신용카드 결제: {amount:,}")
        print(f"   카드: {self.card_number[-4:].rjust(16, '*')}")
        return True

    def refund(self, amount):
        print(f"💳 신용카드 환불: {amount:,}")
        return True

class BankTransfer(PaymentMethod):
    """계좌이체 결제"""

    def __init__(self, account_number, bank):
        self.account_number = account_number
        self.bank = bank

    def pay(self, amount):
        print(f"🏦 계좌이체 결제: {amount:,}")
        print(f"   은행: {self.bank}")
        return True

    def refund(self, amount):
        print(f"🏦 계좌이체 환불: {amount:,}")
        return True

class KakaoPay(PaymentMethod):
    """카카오페이 결제"""

    def __init__(self, phone_number):
        self.phone_number = phone_number

    def pay(self, amount):
        print(f"💛 카카오페이 결제: {amount:,}")
        print(f"   전화번호: {self.phone_number}")
        return True

    def refund(self, amount):
        print(f"💛 카카오페이 환불: {amount:,}")
        return True

class PaymentProcessor:
    """결제 처리기 - 다형성 활용"""

    def process_payment(self, payment_method: PaymentMethod, amount: int):
        """다형성 - 어떤 결제 수단이든 처리 가능"""
        print(f"\n{'='*50}")
        print(f"결제 처리 시작 ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})")
        print(f"{'='*50}")

        if payment_method.pay(amount):
            print("✅ 결제 성공!")
            return True
        else:
            print("❌ 결제 실패!")
            return False

    def process_refund(self, payment_method: PaymentMethod, amount: int):
        """환불 처리"""
        print(f"\n{'='*50}")
        print("환불 처리 시작")
        print(f"{'='*50}")

        if payment_method.refund(amount):
            print("✅ 환불 성공!")
            return True
        else:
            print("❌ 환불 실패!")
            return False

# 사용
processor = PaymentProcessor()

# 다양한 결제 수단으로 결제
card = CreditCard("1234-5678-9012-3456", "홍길동")
processor.process_payment(card, 50000)

bank = BankTransfer("110-123-456789", "국민은행")
processor.process_payment(bank, 30000)

kakao = KakaoPay("010-1234-5678")
processor.process_payment(kakao, 20000)

# 환불
processor.process_refund(card, 50000)

실행 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
==================================================
결제 처리 시작 (2025-04-07 10:00:00)
==================================================
💳 신용카드 결제: 50,000원
   카드: ************3456
✅ 결제 성공!

==================================================
결제 처리 시작 (2025-04-07 10:00:01)
==================================================
🏦 계좌이체 결제: 30,000원
   은행: 국민은행
✅ 결제 성공!

==================================================
결제 처리 시작 (2025-04-07 10:00:02)
==================================================
💛 카카오페이 결제: 20,000원
   전화번호: 010-1234-5678
✅ 결제 성공!

==================================================
환불 처리 시작
==================================================
💳 신용카드 환불: 50,000원
✅ 환불 성공!

🧪 연습 문제

문제 1: 동물원 시스템

다형성을 활용한 동물원 시스템을 구현하세요:

요구사항:

  1. Animal 추상 클래스
  2. Lion, Elephant, Penguin 구현
  3. 각 동물은 speak(), move() 메서드 구현
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @abstractmethod
    def speak(self):
        pass

    @abstractmethod
    def move(self):
        pass

    def introduce(self):
        return f"{self.name} ({self.age}살)"

class Lion(Animal):
    def speak(self):
        return f"{self.name}: 어흥!!"

    def move(self):
        return f"{self.name}이(가) 달립니다 🦁"

class Elephant(Animal):
    def speak(self):
        return f"{self.name}: 뿌우우~"

    def move(self):
        return f"{self.name}이(가) 천천히 걷습니다 🐘"

class Penguin(Animal):
    def speak(self):
        return f"{self.name}: 꽥꽥!"

    def move(self):
        return f"{self.name}이(가) 뒤뚱뒤뚱 걷습니다 🐧"

# 동물원
zoo = [
    Lion("심바", 5),
    Elephant("점보", 10),
    Penguin("뽀로로", 2)
]

for animal in zoo:
    print(animal.introduce())
    print(animal.speak())
    print(animal.move())
    print()

💡 실전 팁 & 주의사항

Tip 1: 덕 타이핑 활용하기

Python에서는 타입보다 동작이 중요합니다. isinstance() 체크를 최소화하세요.

1
2
3
4
5
6
7
8
# ❌ 타입 체크
def process(obj):
    if isinstance(obj, MyClass):
        obj.method()

# ✅ 덕 타이핑
def process(obj):
    obj.method()  # method()만 있으면 OK!

Tip 2: ABC로 명확한 인터페이스 정의

추상 베이스 클래스를 사용하면 구현해야 할 메서드를 명확히 할 수 있습니다.

1
2
3
4
5
6
from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

Tip 3: 연산자 오버로딩은 직관적으로

연산자를 재정의할 때는 사용자가 예상할 수 있는 동작으로 구현하세요.

1
2
3
4
5
6
7
# ✅ 직관적
def __add__(self, other):
    return Money(self.amount + other.amount)

# ❌ 혼란스러움
def __add__(self, other):
    return Money(self.amount * other.amount)  # +인데 곱셈?

📝 오늘 배운 내용 정리

개념 설명 예시
다형성 같은 인터페이스, 다른 구현 shape.area()
덕 타이핑 타입보다 동작 중시 obj.method()
연산자 오버로딩 연산자 재정의 __add__, __eq__
ABC 추상 베이스 클래스 @abstractmethod

주요 특수 메서드

산술 연산:

  • __add__ (+), __sub__ (-), __mul__ (*), __truediv__ (/)

비교 연산:

  • __eq__ (==), __lt__ (<), __gt__ (>), __le__ (<=), __ge__ (>=)

컨테이너:

  • __len__ (len), __getitem__ ([]), __contains__ (in)

문자열:

  • __str__ (str), __repr__ (repr)

🔗 관련 자료

📚 이전 학습

Day 37: 캡슐화와 정보 은닉 심화 ⭐⭐⭐⭐

어제는 캡슐화 원칙 심화, Private 변수와 Property 데코레이터 고급 활용, 불변 객체(Immutable Object) 설계를 배웠습니다!

📚 다음 학습

Day 39: 특수 메서드 (str, repr 등) ⭐⭐⭐⭐

내일은 __str____repr__ 차이, __call__ 호출 가능 객체, __enter____exit__ (Context Manager), 완전한 특수 메서드 가이드를 배웁니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 38/100 Phase 4: 객체지향 프로그래밍 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.