상속(Inheritance)
2025. 3. 26. 04:04ㆍProgramming Languages/Python
상속(Inheritance)
1. 상속이란?
상속은 한 클래스가 다른 클래스의 속성과 메서드를 물려받는 것을 말합니다. 이를 통해 코드 재사용성을 높이고 계층적인 관계를 구현할 수 있습니다.
2. 상속의 이론적 개념
2.1 상속의 핵심 개념
- IS-A 관계: 상속은 "IS-A" 관계를 표현합니다. 예를 들어 "고양이는 동물이다(Cat IS-A Animal)". 이 관계는 자식 클래스가 부모 클래스의 특수한 형태임을 의미합니다.
- 코드 재사용성: 상속의 주요 이점 중 하나는 기존 코드를 재사용하여 개발 시간을 단축하고 코드 중복을 줄이는 것입니다.
- 확장성: 기존 클래스의 기능을 변경하지 않고 새로운 기능을 추가할 수 있습니다.
2.2 상속의 종류
- 단일 상속(Single Inheritance): 한 클래스가 하나의 부모 클래스만 상속받는 형태입니다.
- 다중 상속(Multiple Inheritance): 한 클래스가 여러 부모 클래스를 상속받는 형태로, 파이썬에서 지원합니다.
- 다단계 상속(Multilevel Inheritance): A -> B -> C와 같이 상속이 연속적으로 이루어지는 형태입니다.
2.3 메서드 해결 순서(MRO, Method Resolution Order)
- 파이썬에서 다중 상속 시 메서드 호출 순서를 결정하는 알고리즘입니다.
- C3 선형화 알고리즘을 사용하여 메서드 호출 순서를 결정합니다.
- ClassName.__mro__ 또는 ClassName.mro()로 확인할 수 있습니다.
# MRO 예제
class A:
def method(self):
return "A의 메서드"
class B(A):
def method(self):
return "B의 메서드"
class C(A):
def method(self):
return "C의 메서드"
class D(B, C):
pass
print(D.mro()) # 메서드 해결 순서 출력
# 출력: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
d = D()
print(d.method()) # 출력: B의 메서드 (B가 MRO에서 C보다 앞에 있기 때문)
2.4 상속과 객체지향 설계 원칙
- 리스코프 치환 원칙(Liskov Substitution Principle): 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대체하여 사용할 수 있어야 합니다.
- 개방-폐쇄 원칙(Open-Closed Principle): 클래스는 확장에는 열려 있고, 수정에는 닫혀 있어야 합니다. 상속은 이 원칙을 구현하는 방법 중 하나입니다.
2.5 상속의 장단점
- 장점:
- 코드 재사용성 향상
- 계층적 관계 표현 가능
- 확장성 제공
- 단점:
- 과도한 상속은 코드를 복잡하게 만들 수 있음
- 부모 클래스 변경 시 자식 클래스에 영향을 줄 수 있음 (결합도 증가)
- 다중 상속 시 다이아몬드 문제 발생 가능
3. 상속 구현하기
# 부모 클래스 (기본 클래스)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "동물이 소리를 냅니다."
# 자식 클래스 (파생 클래스)
class Cat(Animal): # Animal 클래스를 상속
def speak(self): # 메서드 오버라이딩
return f"{self.name}가 야옹하고 울어요."
# 또 다른 자식 클래스
class Dog(Animal): # Animal 클래스를 상속
def speak(self): # 메서드 오버라이딩
return f"{self.name}가 멍멍하고 짖어요."
# 인스턴스 생성 및 메서드 호출
animal = Animal("동물")
cat = Cat("나비")
dog = Dog("멍멍이")
print(animal.speak()) # 출력: 동물이 소리를 냅니다.
print(cat.speak()) # 출력: 나비가 야옹하고 울어요.
print(dog.speak()) # 출력: 멍멍이가 멍멍하고 짖어요.
4. super() 함수
super() 함수는 부모 클래스의 메서드를 호출할 때 사용합니다. 특히 자식 클래스에서 부모 클래스의 메서드를 확장할 때 유용합니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "동물이 소리를 냅니다."
class Dog(Animal):
def __init__(self, name, breed):
# 부모 클래스의 __init__ 메서드 호출
super().__init__(name)
self.breed = breed
def speak(self):
# 부모 클래스의 speak 메서드를 호출하고 추가 기능 구현
parent_message = super().speak()
return f"{parent_message} 그리고 {self.name}는 {self.breed} 종이며 멍멍하고 짖습니다."
# 인스턴스 생성
my_dog = Dog("멍멍이", "진돗개")
print(my_dog.speak())
# 출력: 동물이 소리를 냅니다. 그리고 멍멍이는 진돗개 종이며 멍멍하고 짖습니다.
5. 다중 상속
파이썬은 여러 클래스로부터 상속받는 다중 상속을 지원합니다.
class Flyable:
def fly(self):
return "날 수 있습니다!"
class Swimmable:
def swim(self):
return "수영할 수 있습니다!"
class Duck(Animal, Flyable, Swimmable):
def speak(self):
return f"{self.name}가 꽥꽥 소리를 냅니다."
# 인스턴스 생성
duck = Duck("Donald")
print(duck.speak()) # 출력: Donald가 꽥꽥 소리를 냅니다.
print(duck.fly()) # 출력: 날 수 있습니다!
print(duck.swim()) # 출력: 수영할 수 있습니다!
6. 상속 관련 유용한 함수와 속성
6.1 isinstance() 함수
객체가 특정 클래스의 인스턴스인지 또는 해당 클래스의 하위 클래스의 인스턴스인지 확인합니다.
cat = Cat("나비")
print(isinstance(cat, Cat)) # 출력: True
print(isinstance(cat, Animal)) # 출력: True (상속 관계이므로)
print(isinstance(cat, Dog)) # 출력: False
6.2 issubclass() 함수
어떤 클래스가 다른 클래스의 하위 클래스인지 확인합니다.
print(issubclass(Cat, Animal)) # 출력: True
print(issubclass(Dog, Animal)) # 출력: True
print(issubclass(Cat, Dog)) # 출력: False
6.3 bases 속성
클래스의 직계 상위 클래스를 튜플로 반환합니다.
print(Cat.__bases__) # 출력: (<class '__main__.Animal'>,)
print(Duck.__bases__) # 출력: (<class '__main__.Animal'>, <class '__main__.Flyable'>, <class '__main__.Swimmable'>)
7. 실전 활용 예제
7.1 도형 클래스 계층 구조
import math
# 기본 도형 클래스
class Shape:
def area(self):
pass
def perimeter(self):
pass
def describe(self):
return f"이 도형의 면적은 {self.area():.2f}, 둘레는 {self.perimeter():.2f}입니다."
# 원 클래스
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
# 사각형 클래스
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 Square(Rectangle):
def __init__(self, side):
# Rectangle의 __init__을 호출
super().__init__(side, side)
# 도형 인스턴스 생성 및 사용
circle = Circle(5)
rectangle = Rectangle(4, 6)
square = Square(4)
print(circle.describe()) # 출력: 이 도형의 면적은 78.54, 둘레는 31.42입니다.
print(rectangle.describe()) # 출력: 이 도형의 면적은 24.00, 둘레는 20.00입니다.
print(square.describe()) # 출력: 이 도형의 면적은 16.00, 둘레는 16.00입니다.
7.2 은행 계좌 시스템
# 기본 계좌 클래스
class Account:
def __init__(self, account_number, owner, balance=0):
self.account_number = account_number
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
return f"{amount}원이 입금되었습니다. 현재 잔액: {self.balance}원"
return "유효하지 않은 금액입니다."
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
return f"{amount}원이 출금되었습니다. 현재 잔액: {self.balance}원"
return "잔액이 부족하거나 유효하지 않은 금액입니다."
def get_balance(self):
return f"계좌 잔액: {self.balance}원"
# 저축 계좌 (이자 기능 추가)
class SavingsAccount(Account):
def __init__(self, account_number, owner, balance=0, interest_rate=0.01):
super().__init__(account_number, owner, balance)
self.interest_rate = interest_rate
def add_interest(self):
interest = self.balance * self.interest_rate
self.balance += interest
return f"이자 {interest}원이 추가되었습니다. 현재 잔액: {self.balance}원"
# 체크카드 계좌 (카드 결제 기능 추가)
class CheckingAccount(Account):
def __init__(self, account_number, owner, balance=0, overdraft_limit=0):
super().__init__(account_number, owner, balance)
self.overdraft_limit = overdraft_limit
def withdraw(self, amount):
# 당좌대출 한도까지 출금 가능하도록 오버라이딩
if 0 < amount <= (self.balance + self.overdraft_limit):
self.balance -= amount
return f"{amount}원이 출금되었습니다. 현재 잔액: {self.balance}원"
return "출금 한도를 초과했거나 유효하지 않은 금액입니다."
def card_payment(self, amount):
return self.withdraw(amount)
# 계좌 사용 예제
savings = SavingsAccount("1001", "홍길동", 10000, 0.05)
checking = CheckingAccount("1002", "김철수", 5000, 10000)
print(savings.deposit(5000)) # 출력: 5000원이 입금되었습니다. 현재 잔액: 15000원
print(savings.add_interest()) # 출력: 이자 750.0원이 추가되었습니다. 현재 잔액: 15750.0원
print(checking.deposit(1000)) # 출력: 1000원이 입금되었습니다. 현재 잔액: 6000원
print(checking.card_payment(8000)) # 출력: 8000원이 출금되었습니다. 현재 잔액: -2000원
8. 상속 사용 시 주의점
- 깊은 상속 계층 피하기: 너무 깊은 상속 계층은 코드 이해와 유지보수를 어렵게 만듭니다.
- 상속보다 구성 고려: 때로는 상속보다 구성(Composition)이 더 적합할 수 있습니다.
- 인터페이스 상속 vs 구현 상속: 인터페이스를 상속받아 구현하는 방식과 기능 구현까지 상속받는 방식의 차이를 이해해야 합니다.
- 메서드 오버라이딩 시 주의: 부모 클래스의 메서드 동작을 완전히 변경하는 오버라이딩은 리스코프 치환 원칙을 위반할 수 있습니다.
- 다중 상속 사용 시 주의: 다중 상속은 복잡성을 증가시킬 수 있으므로 신중하게 사용해야 합니다.
'Programming Languages > Python' 카테고리의 다른 글
다형성 (Polymorphism) (0) | 2025.03.26 |
---|---|
캡슐화(Encapsulation) (0) | 2025.03.26 |
클래스와 객체지향 프로그래밍 (0) | 2025.03.26 |
함수 디버깅과 테스트 (0) | 2025.03.26 |
함수의 효율성과 성능 최적화 (0) | 2025.03.26 |