logo

[Python] 상속

상속이란?

상속은 객체지향 프로그래밍(OOP)의 핵심 개념 중 하나로, 특정 클래스(부모 클래스 또는 슈퍼 클래스)의 속성과 메소드를 다른 클래스(자식 클래스 또는 서브 클래스)가 받아 사용할 수 있게 해주는 메커니즘을 말합니다. 이를 통해 코드의 재사용성이 높아지고, 소프트웨어의 유지 보수가 용이해지며, 전반적인 코드의 가독성이 향상됩니다.

 

상속의 필요성 및 이점

 

코드 재사용성 증가

상속을 활용하면 기존에 작성된 클래스 코드를 재사용하여 새로운 클래스를 만들 수 있습니다. 이는 개발 시간을 줄이고, 코드의 중복을 방지합니다. 예를 들어, '동물' 클래스에는 '먹는다', '잔다'와 같은 공통의 메소드가 있을 수 있습니다. '강아지'와 '고양이' 클래스가 이 '동물' 클래스로부터 상속받으면, 각각의 클래스에서 '먹는다', '잔다' 메소드를 새롭게 작성하지 않아도 되므로 코드 재사용성이 증가합니다.

 

유지 보수의 용이성

부모 클래스에서 수정이 발생하면, 해당 변경 사항이 자동적으로 자식 클래스에도 반영됩니다. 즉, 코드의 중복 수정이 필요 없어 유지 보수가 용이해집니다. 이는 소프트웨어의 안정성을 높이고, 수정에 따른 오류 발생 가능성을 줄입니다.

 

가독성 향상

코드의 구조가 명확해지고, 각 클래스의 역할이 분명해짐으로써 전반적인 코드의 가독성이 향상됩니다. 객체지향 프로그래밍의 다른 원칙들과 함께 상속을 적절히 활용하면, 복잡한 로직도 이해하기 쉬운 구조로 설계할 수 있습니다.

 

Python에서의 상속 예시

Python에서 상속을 구현하는 방법은 매우 간단합니다. 다음은 간단한 상속의 예시 코드입니다:

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

    def eat(self):
        print(f"{self.name} is eating.")

    def sleep(self):
        print(f"{self.name} is sleeping.")

class Dog(Animal):
    def bark(self):
        print(f"{self.name} is barking.")

class Cat(Animal):
    def meow(self):
        print(f"{self.name} is meowing.")

위 예시에서, DogCat 클래스는 Animal 클래스로부터 상속을 받습니다. 이로 인해 DogCat 인스턴스는 eatsleep 메소드를 사용할 수 있게 됩니다. 추가적으로, 각각의 클래스에는 고유한 행동을 나타내는 barkmeow 메소드가 정의되어 있습니다.

이와 같이 상속을 통해 Python 프로그래밍에서 코드 재사용성을 높이고, 유지 보수의 용이성을 제공하며, 가독성을 향상시킬 수 있습니다.


 

Python에서 클래스 상속 기본

Python에서 클래스 상속(class inheritance)은 한 클래스가 다른 클래스의 속성(attributes)과 메서드(methods)를 상속받아 사용할 수 있게 하는 기능입니다. 이를 통해 기존 코드의 재사용성을 높이고, 코드의 중복을 줄이며, 프로그램의 구조를 개선할 수 있습니다.

부모 클래스와 자식 클래스 정의 방법

상속을 이용하기 위해서는 먼저 부모 클래스(parent class)가 정의되어 있어야 합니다. 이 부모 클래스는 다른 클래스에게 속성이나 메서드를 제공하는 역할을 합니다. 그 후에, 이 부모 클래스를 상속 받아 새로운 속성이나 메서드를 추가하거나 기존의 것을 수정하여 활용하는 자식 클래스(child class)를 정의할 수 있습니다.

부모 클래스(ParentClass) 정의 예시:

class ParentClass:
    def __init__(self):
        self.value = "이것은 부모 클래스입니다."

    def show(self):
        print(self.value)

자식 클래스(ChildClass) 정의 예시:

class ChildClass(ParentClass):  # ParentClass를 상속받음
    pass

class ChildClass(ParentClass): 형식 소개

위 예시에서 볼 수 있듯이, 자식 클래스를 정의할 때는 클래스 이름 뒤에 괄호를 붙이고, 그 안에 상속 받고자 하는 부모 클래스의 이름을 명시합니다. class ChildClass(ParentClass): 형태입니다. 이를 통해 ChildClassParentClass의 모든 속성과 메서드를 상속받게 됩니다.

초기화 메소드 __init__에서 상속 사용

상속받은 자식 클래스 내에서 부모 클래스의 __init__ 메서드(초기화 메서드)를 사용하고 싶은 경우, super() 함수를 활용하여 부모 클래스의 메서드를 호출할 수 있습니다. 이를 통해 부모 클래스가 초기화하는 속성들을 자식 클래스에서도 초기화할 수 있습니다.

예시:

class ParentClass:
    def __init__(self, value):
        self.value = value

    def show(self):
        print(self.value)

class ChildClass(ParentClass):
    def __init__(self, value, childValue):
        super().__init__(value)  # 부모 클래스의 __init__ 메서드 호출
        self.childValue = childValue  # 자식 클래스 고유의 속성 추가

    def showChild(self):
        print(self.childValue)

# 사용 예
parent = ParentClass("부모 클래스 값")
parent.show()  # "부모 클래스 값"

child = ChildClass("부모 클래스 값", "자식 클래스 값")
child.show()  # 상속받은 메서드 사용, "부모 클래스 값"
child.showChild()  # 자식 클래스 고유 메서드, "자식 클래스 값"

이 예제에서 ChildClassParentClass로부터 value 속성과 show() 메서드를 상속 받아 사용합니다. 그리고 ChildClass 자신의 __init__ 메서드 내에서 super().__init__(value)를 호출하여 부모 클래스의 초기화 메쏘드도 실행시켜, value 속성을 초기화합니다. 추가로, 자식 클래스만의 고유한 속성인 childValue를 정의하고 초기화하였습니다.

이렇게 Python에서 상속을 활용하면, 코드의 재사용성과 가독성을 높이면서도, 유연하고 확장 가능한 프로그램 구조를 설계할 수 있습니다.


 

상속의 유형

Python에서 클래스 상속은 부모 클래스의 속성과 메소드를 자식 클래스에서 가져와 사용할 수 있게 해주는 기능입니다. 그리고 상속은 주로 코드의 재사용성을 높이고 복잡성을 줄이는 데 사용됩니다. Python은 다양한 유형의 상속을 지원합니다: 단일 상속 및 다중 상속이 그것입니다.

 

단일 상속

단일 상속은 한 개의 부모 클래스에서만 상속을 받는 것을 의미합니다. 이 경우, 자식 클래스는 하나의 부모 클래스에서 정의된 속성과 메소드를 사용할 수 있습니다.

예제 코드로 단일 상속 구현

class Parent:
    def __init__(self):
        self.parent_attribute = '부모 클래스 속성'

    def parent_method(self):
        print('부모 클래스 메소드')

class Child(Parent):
    def child_method(self):
        print('자식 클래스 메소드')

# 자식 클래스 인스턴스 생성
child_instance = Child()

# 부모 클래스의 메소드 호출
child_instance.parent_method()  # "부모 클래스 메소드" 출력

# 자식 클래스의 메소드 호출
child_instance.child_method()   # "자식 클래스 메소드" 출력
 

다중 상속

다중 상속은 두 개 이상의 부모 클래스에서 상속을 받는 경우를 말합니다. 이를 통해 자식 클래스는 여러 부모 클래스에서 정의된 속성과 메소드에 접근할 수 있습니다.

예제 코드로 다중 상속 구현

class Mother:
    def mother_method(self):
        print('어머니 클래스 메소드')

class Father:
    def father_method(self):
        print('아버지 클래스 메소드')

class Child(Mother, Father):
    def child_method(self):
        print('자식 클래스 메소드')

# 자식 클래스 인스턴스 생성
child_instance = Child()

# 부모 클래스의 메소드 호출
child_instance.mother_method()  # "어머니 클래스 메소드" 출력
child_instance.father_method()  # "아버지 클래스 메소드" 출력

# 자식 클래스의 메소드 호출
child_instance.child_method()   # "자식 클래스 메소드" 출력

다이아몬드 상속 문제 및 해결 방법

다중 상속의 복잡한 경우 중 하나는 "다이아몬드 상속" 문제입니다. 다이아몬드 상속은 다중 상속의 상속 체인에서 최상위 클래스가 두 개 이상의 경로를 통해 자식 클래스에 상속되는 경우를 말합니다. 이러한 상황은 메소드 해석 순서(MRO, Method Resolution Order)가 모호해져 어떤 부모 클래스의 메소드를 호출해야 할지 혼란을 야기할 수 있습니다.

Python은 이 문제를 MRO를 명확하게 정의함으로써 해결합니다. Python에서는 C3 선형화 알고리즘을 사용하여 클래스의 메소드 호출 순서를 결정합니다. 클래스의 __mro__ 속성이나 mro() 메소드를 사용하여 MRO를 확인할 수 있습니다.

class A:
    def method(self):
        print("A 클래스의 메소드")

class B(A):
    pass

class C(A):
    def method(self):
        print("C 클래스의 메소드")

class D(B, C):
    pass

d_instance = D()
d_instance.method()  # C 클래스의 메소드 출력
print(D.mro())  # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] 출력

이 예시에서 D 클래스의 인스턴스가 method()를 호출했을 때, C 클래스의 메소드가 호출됩니다. 이것은 Python이 MRO를 통해 C 클래스를 B 클래스보다 먼저 고려하기 때문입니다. MRO에 따라 모호함 없이 메소드가 결정됩니다.


메소드 오버라이딩(Method Overriding)은 객체지향 프로그래밍에서 중요한 메커니즘 중 하나입니다. 이를 통해, 자식 클래스는 상속받은 부모 클래스의 메소드를 자체적으로 재정의할 수 있습니다. 이 과정을 메소드 오버라이딩이라고 합니다.

자식 클래스에서 부모 클래스의 메소드를 재정의하는 방법

메소드 오버라이딩은 자식 클래스에서, 상속받은 부모 클래스의 메소드와 같은 이름, 같은 매개변수로 메소드를 다시 정의하는 것을 의미합니다. 이렇게 함으로써, 해당 메소드를 호출할 때, 부모 클래스의 메소드가 아닌 오버라이드된 자식 클래스의 메소드가 실행됩니다.

오버라이딩의 목적 및 사용법 설명

메소드 오버라이딩의 주된 목적은 상속받은 클래스의 기능을 확장하거나 변경하기 위함입니다. 자식 클래스는 부모 클래스의 메소드를 상속받기는 하지만, 특정 상황에 맞게 해당 메소드의 기능을 수정하거나 추가하는 데 필요할 때 메소드 오버라이딩을 사용할 수 있습니다.

예제 코드로 메소드 오버라이딩 설명

다음은 메소드 오버라이딩의 기본적인 예제 코드입니다.

class Animal:
    def speak(self):
        return "This animal does not have a specific sound."

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# 객체 생성
dog = Dog()
cat = Cat()

# 메소드 호출
print(dog.speak())  # 출력: Woof!
print(cat.speak())  # 출력: Meow!

이 예제에서 Animal 클래스는 기본적인 speak() 메소드를 가지고 있으며, "This animal does not have a specific sound."라는 문자열을 반환합니다. DogCat 클래스는 Animal 클래스를 상속받고 있지만, speak() 메소드를 각각 오버라이딩하여 강아지와 고양이의 울음 소리를 반환하도록 했습니다. 결과적으로, DogCat 객체에서 speak() 메소드를 호출할 때, 각각 오버라이드된 메소드가 실행되어 "Woof!"와 "Meow!"라는 결과를 얻게 됩니다.

이처럼 메소드 오버라이딩은 부모 클래스로부터 상속받은 메소드의 기능을 자식 클래스에서 필요에 맞게 재정의할 수 있는 강력한 메커니즘입니다. 따라서 코드의 재사용성을 높이고, 유연성을 증가시키는데 큰 역할을 합니다.


 

super() 함수

 

super() 함수란?

상속 관계에 있는 클래스 사이에서, 자식 클래스가 부모 클래스의 메소드나 속성에 접근할 수 있게 해주는 내장 함수입니다. 기본적으로, 자식 클래스에서 부모 클래스의 메소드를 호출하거나 속성에 접근하기 위해 사용됩니다. 이를 통해 다형성(polymorphism)과 코드의 재사용성을 증가시킬 수 있습니다.

 

super() 함수의 사용법과 목적

super() 함수는 자식 클래스에서 부모 클래스의 메소드를 호출할 때 사용하는데, 주로 생성자 내에서 부모의 생성자를 호출할 때나 부모 클래스의 메소드를 오버라이딩(재정의)한 경우, 원래의 부모 클래스 메소드를 호출할 때 사용됩니다. 목적은 다음과 같습니다:

  • 코드의 중복을 줄임: 부모 클래스의 메소드를 직접 명시하지 않고도 호출할 수 있어, 코드의 중복을 줄일 수 있습니다.
  • 유지보수의 용이성: 부모 클래스의 메소드나 속성이 변경되어도 자식 클래스의 코드를 수정할 필요가 없어, 유지보수가 용이해집니다.
  • 다형성 실현: 다양한 종류의 객체가 같은 인터페이스를 공유할 수 있도록 하여, 코드의 유연성을 높입니다.
 

예제 코드를 통한 super() 함수 설명

아래 예제에서는 super() 함수를 사용하는 방법을 보여줍니다. 부모 클래스인 Person 클래스에는 __init__ 생성자가 있으며, 자식 클래스인 Employee 클래스에서 이 생성자를 super()를 사용하여 호출합니다. 이 예제에서 EmployeePerson의 기능을 확장하여 position 속성을 추가합니다.

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

class Employee(Person):
    def __init__(self, name, age, position):
        super().__init__(name, age)  # super() 함수를 사용하여 부모 클래스의 생성자 호출
        self.position = position

# 객체 생성 및 사용
emp = Employee("John Doe", 30, "Engineer")
print(f"Name: {emp.name}, Age: {emp.age}, Position: {emp.position}")

이 코드에서 super().__init__(name, age)를 통해 Employee 클래스의 인스턴스를 생성할 때, 부모 클래스인 Person의 생성자도 함께 호출됩니다. 이를 통해 nameage 속성이 적절히 초기화됩니다. 그 후 Employee 클래스에서는 추가적으로 position 속성도 설정합니다.

결과적으로, super() 함수를 사용함으로써 자식 클래스는 부모 클래스의 구현을 재활용하되 자신만의 추가적인 특성을 더할 수 있게 됩니다. 이러한 방식은 객체지향 프로그래밍의 핵심 원칙 중 하나인 코드의 재사용성과 확장성을 실현하는 데 중요한 역할을 합니다.


상속의 메커니즘 이해는 객체지향 프로그래밍의 핵심 중 하나로, 특히 Python에서 다양한 기능의 재사용 및 확장성을 제공합니다. 이 부분에서는 특히 다중 상속 시 발생할 수 있는 메소드 결정 순서(MRO)와 클래스의 관계를 확인하는 issubclass()isinstance() 함수에 대해 자세히 다룹니다.

 

MRO(Method Resolution Order)

다중 상속 환경에서 파이썬은 메소드 결정 순서(MRO)를 사용하여 어떤 부모 클래스의 메소드를 호출할지를 결정합니다. 이는 복잡할 수 있는 다중 상속의 메소드 호출 순서를 명확하게 해주는 중요한 메커니즘입니다.

  • MRO를 확인하는 방법
    • __mro__ 속성: 특정 클래스의 MRO를 튜플 형태로 확인할 수 있습니다.
    • mro() 메소드: 해당 클래스의 MRO 리스트를 반환하는 메소드입니다.
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)  # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(D.mro())    # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

이 예시는 D 클래스가 BC를 다중 상속받고 있으며, BC는 각각 A를 상속받고 있습니다. MRO를 통해 D 클래스의 메소드 호출 순서는 D, B, C, A, 그리고 최상위 object 클래스 순임을 알 수 있습니다.

 

issubclass()와 isinstance() 함수

  • issubclass() 함수: 주어진 두 클래스의 상속 관계를 확인합니다. 첫 번째 인자가 두 번째 인자의 서브 클래스인지 여부를 반환합니다.
print(issubclass(D, A))  # True
print(issubclass(B, D))  # False
  • isinstance() 함수: 주어진 인스턴스가 특정 클래스/클래스 튜플의 인스턴스인지 혹은 그 서브 클래스의 인스턴스인지 확인합니다.
d_instance = D()
print(isinstance(d_instance, D))  # True
print(isinstance(d_instance, A))  # True
print(isinstance(d_instance, object))  # True
print(isinstance(d_instance, B))  # True
print(isinstance(d_instance, C))  # True

이 함수들은 객체의 타입을 확인하거나, 특정 클래스의 하위 클래스인지를 확인하는 데 유용하게 사용됩니다. 특히, 다형성을 활용하는 객체 지향 프로그래밍에서 강력한 도구로 작용합니다.

위 예시와 설명을 통해 issubclass()isinstance() 함수의 작동 방식과 사용법을 이해할 수 있으며, 이것들은 Python에서 클래스 간의 관계를 파악하고 관리하는 데 필수적인 도구입니다.


실용적인 상속 사용 사례에 대해 상세하게 살펴봅시다. 상속은 코드의 재사용성을 높이고, 유지 보수를 용이하게 하며, 코드의 가독성을 향상시키는 데 큰 역할을 합니다. 아래의 사용 사례는 상속이 실제 프로그래밍에서 어떻게 활용될 수 있는지 보여 줄 것입니다.

 

1. 추상 베이스 클래스의 구현

추상 베이스 클래스(ABC)는 하위 클래스가 반드시 구현해야 하는 메소드들의 목록을 제공합니다. 이는 공통의 인터페이스를 갖는 다양한 클래스들을 작성할 때 유용합니다.

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def move(self):
        pass

class Car(Vehicle):
    def move(self):
        return "The car is moving."

class Boat(Vehicle):
    def move(self):
        return "The boat is sailing."

이 예제에서는 Vehicle 이라는 ABC를 정의하고, 이 클래스를 CarBoat 클래스에서 상속받아 move 메소드를 각각 구현했습니다. 이 방법으로 move 메소드가 각 차량에 맞게 적절히 구현될 것임을 보장합니다.

 

2. 상속을 이용한 코드 구조화와 확장

상속은 코드의 구조화와 확장을 보다 간편하게 해 줍니다. 공통적인 기능을 부모 클래스에, 특수한 기능은 자식 클래스에 두어 코드의 재사용성을 높일 수 있습니다.

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

    def speak(self):
        raise NotImplementedError("Subclasses must implement this method")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

이 예제에서는 Animal 클래스에 모든 동물이 공유하는 이름 속성을 정의했고, speak 메소드를 통해 각 동뜨의 특별한 "말하기" 기능을 자식 클래스에서 구현하도록 했습니다. 이러한 구조는 새로운 동물 종을 추가할 때 Animal 클래스를 상속받아 쉽게 확장할 수 있습니다.

 

결론

상속은 프로그램의 다양한 부분에서 코드의 재사용, 구조화, 확장성을 개선하는 강력한 도구입니다. 추상 베이스 클래스의 사용은 공통 인터페이스의 정의를 강제하며, 상속을 이용한 코드 구조화는 유지 보수와 확장을 용이하게 합니다. 이러한 실용적인 상속 사용 사례를 이해하고 적용함으로써, 보다 깨끗하고 효율적인 코드를 작성할 수 있습니다.

Previous
소멸자