파이썬 상속과 오버라이딩, 클래스를 또 만들지 마세요
지난 글에서 __init__으로 인스턴스 만드는 걸 해봤잖아요. 그때 강아지, 고양이, 햄스터 클래스를 비슷하게 또 만들면서 좀 부담스러우셨죠. 저도 처음 클래스를 배울 때 이 부분이 제일 답답했어요. 분명히 거의 똑같은 코드인데, 동물 종류가 늘어날 때마다 전체를 다시 써야 하니까요.
사실 이걸 한 방에 해결하는 장치가 따로 있어요. 상속이라는 건데, 이름은 거창하지만 알고 보면 별거 없거든요.
파이썬 상속이 뭔가요
상속은 말 그대로 부모한테 뭔가를 물려받는 거예요. 부모 클래스가 가진 속성과 메서드를 자식 클래스가 그대로 가져다 쓰는 장치입니다.
비유로 생각해보세요. 강아지도 결국 동물이잖아요. 동물이 가진 특징(숨을 쉰다, 먹는다)은 강아지도 다 가지고 있죠. 그러면 굳이 강아지 클래스에 "숨을 쉰다"를 또 적을 필요가 없는 거예요. 동물 클래스에서 이미 만들어둔 걸 그대로 받아 쓰면 되니까요.
문법은 이렇게 생겼어요.
class Dog(Animal): # 괄호 안에 부모 이름
pass
Animal이 부모, Dog가 자식입니다. 이렇게만 써놔도 Dog는 Animal이 가진 모든 걸 자동으로 다 가지게 돼요.
파이썬 상속 예제, Animal에서 Dog로
말로만 하면 와닿지 않으니 바로 코드로 보여드릴게요.
# 부모 클래스
class Animal:
def __init__(self, name):
self.name = name # 모든 동물은 이름이 있어요
def breathe(self): # 숨 쉬는 동작
print(f"{self.name}이(가) 숨을 쉽니다.")
# 자식 클래스 - 아직 아무것도 추가 안 함
class Dog(Animal):
pass
dog1 = Dog("뽀삐") # Animal의 __init__이 그대로 실행됨
dog1.breathe() # 뽀삐이(가) 숨을 쉽니다.
신기하죠. Dog 안에는 pass밖에 없는데 breathe()가 멀쩡히 돌아가요. Animal에서 다 받아왔으니까요.
여기서 자식만의 메서드를 추가하는 것도 자유예요.
class Dog(Animal):
def fetch(self): # 자식만의 새 동작
print(f"{self.name}이(가) 공을 물어옵니다.")
dog1 = Dog("뽀삐")
dog1.breathe() # 부모한테서 받은 메서드
dog1.fetch() # 자식만의 메서드
부모 거 그대로 쓰면서, 자식 고유의 행동을 더 얹는 거죠.
python 오버라이딩, 같은 이름인데 다르게
이번엔 좀 다른 상황이에요. 부모가 정의한 메서드를 자식에서 다르게 동작하게 만들고 싶을 때가 있거든요. 예를 들어 동물은 다 "소리를 낸다"지만, 강아지는 멍멍, 고양이는 야옹이잖아요.
이럴 때 자식 클래스에서 부모와 똑같은 이름의 메서드를 다시 쓰면 돼요. 이걸 오버라이딩이라고 합니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self): # 부모의 기본 동작
print(f"{self.name}이(가) 소리를 냅니다.")
class Dog(Animal):
def speak(self): # 부모와 같은 이름, 다른 동작
print(f"{self.name}: 멍멍!")
class Cat(Animal):
def speak(self): # 고양이는 따로 정의
print(f"{self.name}: 야옹~")
Dog("뽀삐").speak() # 뽀삐: 멍멍!
Cat("나비").speak() # 나비: 야옹~
부모 메서드를 자식이 자기 식으로 덮어쓴 거예요. 같은 speak()라는 이름인데 누가 호출했냐에 따라 결과가 달라지죠. 이게 객체지향에서 굉장히 자주 쓰이는 패턴이에요.
참고로 이름이 비슷한 "오버로딩"이라는 게 있는데, 그건 좀 다른 개념이고 파이썬에서는 거의 안 써요. 헷갈리지 마세요.
super()는 왜 필요할까
자, 여기서 가장 헷갈리는 부분이 등장합니다. 자식이 자기만의 __init__을 따로 만들고 싶을 때가 있어요. 예를 들어 강아지에게 품종(breed)이라는 속성을 추가하고 싶다고 해볼게요.
이때 그냥 자식 __init__을 쓰면, 부모 __init__은 자동으로 안 돌아가요. 그래서 부모가 만들어주던 self.name이 통째로 사라져버립니다.
이걸 막으려면 자식 안에서 부모 생성자를 직접 한 번 호출해줘야 해요. 그게 바로 super().__init__()입니다.
저는 이걸 "부모한테 한번 인사하고 들어간다"라고 외웠어요. 자식 일을 시작하기 전에, 부모가 원래 하던 세팅을 한 번 부탁하는 거죠.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 부모한테 name 처리 부탁
self.breed = breed # 그 다음 내 일
dog1 = Dog("뽀삐", "푸들")
print(dog1.name) # 뽀삐 (부모가 세팅)
print(dog1.breed) # 푸들 (자식이 세팅)
만약 super().__init__(name) 줄을 깜빡하고 빼먹으면 어떻게 될까요.
class Dog(Animal):
def __init__(self, name, breed):
# super().__init__(name) ← 깜빡!
self.breed = breed
dog1 = Dog("뽀삐", "푸들")
print(dog1.name)
# AttributeError: 'Dog' object has no attribute 'name'
처음 이 에러 만나면 진짜 당황스러워요. 분명히 Dog("뽀삐", ...)로 이름을 줬는데 name이 없다고 하니까요. 원인은 부모 생성자가 안 돌아서 self.name이 만들어지지 않은 겁니다. super 한 줄이 빠졌을 뿐인데 이런 결과가 나와요.
실전 예제, User와 Admin
좀 더 현실적인 예제도 보여드릴게요. 회원 시스템에서 일반 유저와 관리자를 구분한다고 해볼게요.
class User:
def __init__(self, username):
self.username = username
def login(self): # 모든 유저 공통 동작
print(f"{self.username} 로그인")
class Admin(User):
def __init__(self, username, level):
super().__init__(username) # 부모가 username 세팅
self.level = level # 관리자 권한 레벨
def delete_user(self, target): # 관리자만의 권한
print(f"{self.username}(레벨 {self.level})이(가) {target} 삭제")
a = Admin("관리자1", 5)
a.login() # 부모 메서드 그대로
a.delete_user("악성유저") # 자식만의 권한
Admin도 결국 User잖아요. 로그인은 똑같이 되고, 거기에 삭제 권한이 더 붙은 것뿐이에요. 이런 식으로 위에는 공통 기능을 두고 아래로 갈수록 권한이 추가되는 구조가 실제 서비스에서 정말 많이 나옵니다.
참고로 부모를 여러 개 동시에 상속하는 다중 상속(class C(A, B):)도 있긴 한데, 규칙이 좀 까다로워서 입문 단계에서는 일단 모른 척하고 넘어가셔도 돼요.
직접 해보세요
오늘 배운 걸로 간단한 과제 하나 드릴게요.
Vehicle부모 클래스를 만드세요.brand속성과move()메서드를 가집니다.Car자식 클래스를 만들고move()를 "부릉부릉"으로 오버라이딩하세요.Bike자식 클래스도 만들어서move()를 "따르릉"으로 바꿔보세요.- 둘 다
super().__init__()으로 brand를 받아 처리하면 완성입니다.
직접 쳐보시면 super가 왜 필요한지 몸으로 느끼실 수 있을 거예요. 막히는 부분 있으시면 댓글로 남겨주세요.
객체지향 기본기는 여기까지예요. 다음 글에서는 OOP에서 잠깐 떨어져서, 코드를 짧게 쓰는 기법 하나를 보여드릴 건데요. 리스트 컴프리헨션이라고 하는 건데, 처음 보면 외계어 같은데 한 번 익숙해지면 다시는 못 돌아갑니다.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 파이썬 람다와 map filter, 한 줄 함수로 코드 짧게 쓰는 법 (0) | 2026.05.14 |
|---|---|
| [Python] 파이썬 리스트 컴프리헨션, 코드 한 줄로 줄이는 마법 (0) | 2026.05.13 |
| [Python] 파이썬 생성자(__init__) 쉽게 이해하기 (0) | 2026.05.11 |
| [Python] 파이썬 클래스, 객체지향이란 무엇일까 (0) | 2026.05.08 |
| [Python] 파이썬 계산기 만들기 (첫 미니 프로젝트) (0) | 2026.05.07 |