Application/Python

[Python] 파이썬 상속과 오버라이딩, 클래스를 또 만들지 마세요

devsalix 2026. 5. 12. 11:00
728x90

파이썬 상속과 오버라이딩, 클래스를 또 만들지 마세요

지난 글에서 __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에서 잠깐 떨어져서, 코드를 짧게 쓰는 기법 하나를 보여드릴 건데요. 리스트 컴프리헨션이라고 하는 건데, 처음 보면 외계어 같은데 한 번 익숙해지면 다시는 못 돌아갑니다.


 


제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀

 

 
728x90