지난 편에서는 부모 클래스의 속성과 메서드를 그대로 물려받아 코드 중복을 획기적으로 줄이는 상속(Inheritance)의 기본 개념을 알아보았습니다. 부모 클래스를 상속받으면 모든 기능을 공짜로 얻을 수 있어 매우 편리하지만, 실무에서 자식 클래스를 개발하다 보면 부모의 동작 방식을 그대로 쓰는 것만으로는 부족한 상황에 직면하게 됩니다.
예를 들어 일반 회원의 할인율 계산 로직과 VIP 회원의 할인율 계산 로직이 달라야 하거나, 부모의 기능에 자식만의 특별한 로직을 추가로 덧붙여야 할 때가 있습니다. 부모 코드가 맘에 들지 않는다고 상속을 포기하고 처음부터 다 새로 만들자니 상속의 장점이 사라지게 됩니다.
저 역시 초보 시절, 부모 클래스의 기능을 일부만 수정하고 싶은데 방법을 몰라 자식 클래스에 완전히 새로운 이름의 메서드를 새로 정의했다가, 전체 클래스의 메서드 이름이 제각각으로 꼬여버린 난감한 경험이 있습니다. 오늘은 부모에게 물려받은 메서드를 자식의 입맛에 맞게 재정의하고 덮어쓰는 메서드 오버라이딩(Method Overriding)과 부모의 유산을 안전하게 호출하는 super() 키워드 활용법을 정리해 드립니다.
1. 메서드 오버라이딩(Method Overriding)이란 무엇인가?
'오버라이딩(Overriding)'은 단어 의미 그대로 '기존의 것을 올라타서 덮어쓴다'는 뜻입니다.
상속 관계에서 부모 클래스에 이미 정의되어 있는 메서드를, 자식 클래스에서 동일한 이름과 매개변수로 새로 작성하여 부모의 기능을 덮어씌우는 기술입니다.
오버라이딩을 적용하면 외부에서 자식 객체의 메서드를 호출할 때, 부모의 옛날 메서드가 아니라 자식 클래스에서 새로 재정의한 최신 메서드가 우선하여 실행됩니다.
2. 실전 예제: 자식 클래스에서 메서드 덮어쓰기
할인율 계산 메서드를 자식 클래스 성격에 맞게 오버라이딩하는 실전 코드입니다.
# 부모 클래스
class Member:
def __init__(self, name, price):
self.name = name
self.price = price
# 기본 할인율 계산 메서드 (일반 회원 5% 할인)
def calculate_discount(self):
discount_price = self.price * 0.95
print(f"[일반 회원] {self.name}님의 최종 결제 금액: {int(discount_price)}원")
return discount_price
# 자식 클래스 (Member 상속)
class VIPMember(Member):
# 부모의 calculate_discount 메서드를 자식 입맛에 맞춰 오버라이딩 (덮어쓰기)
def calculate_discount(self):
# VIP 회원은 20% 할인 적용
discount_price = self.price * 0.80
print(f"[VIP 특별 혜택] {self.name}님의 최종 결제 금액: {int(discount_price)}원")
return discount_price
이제 객체를 만들어 결과를 비교해 보겠습니다.
user1 = Member("김파이", 10000)
user2 = VIPMember("이객체", 10000)
user1.calculate_discount() # 출력: [일반 회원] 김파이님의 최종 결제 금액: 9500원
user2.calculate_discount() # 출력: [VIP 특별 혜택] 이객체님의 최종 결제 금액: 8000원
user2는 VIPMember 객체이므로 부모의 5% 할인 로직이 아닌, 자식 클래스에서 덮어쓴 20% 할인 로직이 정확히 동작합니다. 이처럼 메서드 이름을 통일하면서도 객체 종류에 따라 다르게 동작하도록 만드는 성질을 객체지향에서는 '다양성(Polymorphism)'이라고 부릅니다.
3. super() 키워드: 부모의 기능을 덮어쓰지 않고 확장하기
메서드 오버라이딩을 할 때 부모의 기존 기능을 완전히 무시하고 새로 쓰는 경우도 있지만, "부모가 해두었던 기존 작업에 내가 필요한 코드 몇 줄만 덧붙이고 싶은" 경우가 훨씬 많습니다.
이때 사용하는 파이썬의 강력한 전용 키워드가 바로 super()입니다. super()는 현재 자식 클래스의 상위 계층인 부모 클래스 객체를 가리키는 통로 역할을 합니다.
특히 생성자(__init__)를 오버라이딩할 때 super().__init__()을 호출하는 것은 필수적인 패턴입니다.
class Member:
def __init__(self, name, email):
self.name = name
self.email = email
class VIPMember(Member):
def __init__(self, name, email, vip_level):
# 1. super()를 사용해 부모 클래스의 __init__을 먼저 호출하여 name, email 초기화 위임
super().__init__(name, email)
# 2. 자식 클래스만의 고유 속성 추가 초기화
self.vip_level = vip_level
def show_info(self):
print(f"VIP 회원: {self.name} | 이메일: {self.email} | 등급: {self.vip_level}")
vip_user = VIPMember("박클래스", "park@python.com", "Gold")
vip_user.show_info()
왜 super().init()을 반드시 써야 할까요?
자식 클래스에서 __init__을 다시 정의(오버라이딩)하면, 파이썬은 부모의 __init__을 자동으로 실행해 주지 않습니다.
만약 super().__init__(name, email) 코드를 빠뜨리면, 부모가 들고 있던 self.name과 self.email 인스턴스 변수들이 초기화되지 않아 추후 해당 변수를 참조할 때 AttributeError가 발생하며 프로그램이 다운됩니다. 따라서 부모의 유산을 안전하게 받으면서 속성을 확장할 때는 반드시 super().__init__()을 첫 줄에 써주어야 합니다.
4. 오버라이딩 시 주의해야 할 2가지 핵심 규칙
1) 메서드 이름과 매개변수 구조 유지
오버라이딩의 목적은 외부에서 동일한 이름으로 메서드를 호출했을 때 객체별로 다르게 반응하게 만드는 것입니다. 자식 클래스에서 메서드 이름을 살짝 바꾸거나 전혀 다른 매개변수 구조를 요구하면 오버라이딩이 아닌 별개의 메서드가 되어버려 코드의 일관성이 깨집니다.
2) 부모 클래스의 계약(규격) 준수
부모 메서드가 특정 형태의 반환값(예: 숫자 리스트)을 리턴하도록 설계되어 있다면, 자식에서 오버라이딩한 메서드 역시 동일한 포맷의 결과를 리턴해야 합니다. 자식 메서드가 갑자기 문자열을 리턴해 버리면 해당 객체를 사용하는 외부 코드에서 예기치 않은 타입 에러가 터지게 됩니다.
핵심요약
메서드 오버라이딩(Overriding)은 부모 클래스에 존재하는 메서드를 자식 클래스에서 동일한 이름으로 재정의하여 기능을 덮어쓰는 기법입니다.
super()키워드를 활용하면 부모 클래스의 메서드를 직접 호출할 수 있으며, 기존 기능을 파괴하지 않고 자식만의 기능을 안전하게 확장할 수 있습니다.자식 클래스에서 생성자(
__init__)를 재정의할 때는 부모 속성의 미초기화 방지를 위해 반드시super().__init__()을 호출해야 합니다.


0 댓글