KR_Inheritance - somaz94/python-study GitHub Wiki

Python 상속과 λ‹€ν˜•μ„± κ°œλ… 정리


1️⃣ 상속 기초

클래슀 κ°„μ˜ μ½”λ“œ μž¬μ‚¬μš©μ„ μœ„ν•œ λ©”μ»€λ‹ˆμ¦˜μ΄λ‹€.

# κΈ°λ³Έ 상속
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return f"{self.name}κ°€ 멍멍!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}κ°€ μ•Όμ˜Ή!"

dog = Dog("멍멍이")
print(dog.speak())  # 멍멍이가 멍멍!

# 상속 관계 확인
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True
print(issubclass(Dog, Animal))  # True

# λ©”μ„œλ“œ μΆ”κ°€
class Bird(Animal):
    def speak(self):
        return f"{self.name}κ°€ μ§Ήμ§Ή!"
    
    def fly(self):
        return f"{self.name}κ°€ λ‚ μ•„κ°‘λ‹ˆλ‹€!"

βœ… νŠΉμ§•:

  • μ½”λ“œ μž¬μ‚¬μš©
  • λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©
  • 계측 ꡬ쑰
  • is-a 관계 ν‘œν˜„
  • 곡톡 μΈν„°νŽ˜μ΄μŠ€ 제곡


2️⃣ 닀쀑 상속

Python은 μ—¬λŸ¬ 클래슀λ₯Ό λ™μ‹œμ— 상속받을 수 μžˆλŠ” 닀쀑 상속을 μ§€μ›ν•œλ‹€.

class Flying:
    def fly(self):
        return "λ‚  수 있음"

class Swimming:
    def swim(self):
        return "μˆ˜μ˜ν•  수 있음"

class Duck(Flying, Swimming):
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name}이 κ½₯κ½₯!"

# 닀쀑 상속 μ‚¬μš©
duck = Duck("λ„λ„λ“œ")
print(duck.fly())    # λ‚  수 있음
print(duck.swim())   # μˆ˜μ˜ν•  수 있음
print(duck.speak())  # λ„λ„λ“œμ΄ κ½₯κ½₯!

# λ©”μ„œλ“œ 해석 μˆœμ„œ(MRO) 확인
print(Duck.__mro__)

# 닀이아λͺ¬λ“œ 문제
class A:
    def who_am_i(self):
        return "I am A"

class B(A):
    def who_am_i(self):
        return "I am B"

class C(A):
    def who_am_i(self):
        return "I am C"

class D(B, C):
    pass

# D의 who_am_iλŠ” B의 λ©”μ„œλ“œκ°€ 호좜됨
print(D().who_am_i())  # I am B
print(D.__mro__)  # λ©”μ„œλ“œ 해석 μˆœμ„œ 확인

βœ… νŠΉμ§•:

  • μ—¬λŸ¬ 클래슀 상속
  • κΈ°λŠ₯ μ‘°ν•©
  • Method Resolution Order (MRO) κ³ λ €
  • 닀이아λͺ¬λ“œ 문제 ν•΄κ²°
  • C3 μ„ ν˜•ν™” μ•Œκ³ λ¦¬μ¦˜ μ‚¬μš©


3️⃣ super() μ‚¬μš©

λΆ€λͺ¨ 클래슀의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κΈ° μœ„ν•œ λ‚΄μž₯ ν•¨μˆ˜μ΄λ‹€.

class Parent:
    def __init__(self, name):
        self.name = name
    
    def greet(self):
        return f"μ•ˆλ…•, λ‚˜λŠ” {self.name}"

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # λΆ€λͺ¨ 클래슀의 __init__ 호좜
        self.age = age
    
    def greet(self):
        return super().greet() + f", {self.age}살이야"

# super() μ‚¬μš©
child = Child("영희", 10)
print(child.greet())  # μ•ˆλ…•, λ‚˜λŠ” 영희, 10살이야

# 닀쀑 μƒμ†μ—μ„œμ˜ super()
class A:
    def method(self):
        print("A.method 호좜")

class B(A):
    def method(self):
        print("B.method 호좜")
        super().method()

class C(A):
    def method(self):
        print("C.method 호좜")
        super().method()

class D(B, C):
    def method(self):
        print("D.method 호좜")
        super().method()

# MROλ₯Ό 따라 D -> B -> C -> A μˆœμ„œλ‘œ λ©”μ„œλ“œ 호좜
D().method()

βœ… νŠΉμ§•:

  • λΆ€λͺ¨ 클래슀 λ©”μ„œλ“œ 호좜
  • μ΄ˆκΈ°ν™” 체인
  • λ©”μ„œλ“œ ν™•μž₯
  • 닀쀑 μƒμ†μ—μ„œμ˜ MRO ν™œμš©
  • μ½”λ“œ 쀑볡 λ°©μ§€
  • μœ μ§€λ³΄μˆ˜μ„± ν–₯상


4️⃣ 좔상 클래슀

좔상 ν΄λž˜μŠ€λŠ” ν•˜μœ„ ν΄λž˜μŠ€κ°€ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜λŠ” 틀이닀.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass

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 Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

# 좔상 ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™”ν•  수 μ—†μŒ
# shape = Shape()  # TypeError λ°œμƒ

# λͺ¨λ“  좔상 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•ŠμœΌλ©΄ μ—λŸ¬ λ°œμƒ
# class InvalidShape(Shape):
#     def area(self):
#         return 0
#     # perimeter λ©”μ„œλ“œκ°€ μ—†μ–΄ μ—λŸ¬ λ°œμƒ

# λ‹€ν˜•μ„± ν™œμš©
shapes = [Rectangle(5, 4), Circle(3)]
for shape in shapes:
    print(f"면적: {shape.area():.2f}, λ‘˜λ ˆ: {shape.perimeter():.2f}")

βœ… νŠΉμ§•:

  • μΈν„°νŽ˜μ΄μŠ€ μ •μ˜
  • κ΅¬ν˜„ κ°•μ œ
  • 섀계 λͺ…ν™•ν™”
  • λ‹€ν˜•μ„± 지원
  • λ©”μ„œλ“œ μ‹œκ·Έλ‹ˆμ²˜ ν‘œμ€€ν™”
  • μ½”λ“œ ν’ˆμ§ˆ κ°œμ„ 


5️⃣ 믹슀인(Mixin) νŒ¨ν„΄

λ―ΉμŠ€μΈμ€ 상속을 톡해 ν΄λž˜μŠ€μ— κΈ°λŠ₯을 μΆ”κ°€ν•˜λŠ” 방법이닀.

class JSONMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class CSVMixin:
    def to_csv(self):
        return ",".join(str(v) for v in self.__dict__.values())

class User(JSONMixin, CSVMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age

user = User("홍길동", 30)
print(user.to_json())  # {"name": "홍길동", "age": 30}
print(user.to_csv())   # 홍길동,30

# λ‹€μ–‘ν•œ 믹슀인 μ‘°ν•©
class LoggingMixin:
    def log(self, message):
        print(f"[LOG] {message}")

class ValidationMixin:
    def validate(self):
        for key, value in self.__dict__.items():
            if value is None:
                return False
        return True

class Product(JSONMixin, LoggingMixin, ValidationMixin):
    def __init__(self, name, price, description=None):
        self.name = name
        self.price = price
        self.description = description
        self.log(f"Product created: {name}")

product = Product("λ…ΈνŠΈλΆ", 1500000)
print(product.validate())  # True
print(product.to_json())   # {"name": "λ…ΈνŠΈλΆ", "price": 1500000, "description": null}

βœ… νŠΉμ§•:

  • κΈ°λŠ₯ μž¬μ‚¬μš©
  • μ½”λ“œ λͺ¨λ“ˆν™”
  • μœ μ—°ν•œ ν™•μž₯
  • 관심사 뢄리
  • 닀쀑 상속 νŒ¨ν„΄
  • 단일 μ±…μž„ 원칙 μ€€μˆ˜
  • ꡬ성 κ°€λŠ₯ν•œ λ™μž‘


6️⃣ λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©κ³Ό λ‹€ν˜•μ„±

상속받은 λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ—¬ λ‹€μ–‘ν•œ ν˜•νƒœλ‘œ λ™μž‘ν•˜κ²Œ ν•˜λŠ” κ°œλ…μ΄λ‹€.

# λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©
class Vehicle:
    def __init__(self, name):
        self.name = name
    
    def start(self):
        return f"{self.name} μ‹œλ™μ„ κ²λ‹ˆλ‹€."
    
    def move(self):
        return f"{self.name} 이동 쀑"

class Car(Vehicle):
    def start(self):
        return f"{self.name} 엔진을 μΌ­λ‹ˆλ‹€."
    
    def move(self):
        return f"{self.name} λ„λ‘œλ₯Ό λ‹¬λ¦½λ‹ˆλ‹€."

class Boat(Vehicle):
    def start(self):
        return f"{self.name} λͺ¨ν„°λ₯Ό κ°€λ™ν•©λ‹ˆλ‹€."
    
    def move(self):
        return f"{self.name} λ¬Ό μœ„λ₯Ό ν•­ν•΄ν•©λ‹ˆλ‹€."

# λ‹€ν˜•μ„± ν™œμš©
vehicles = [Vehicle("μš΄μ†‘μˆ˜λ‹¨"), Car("μ†Œλ‚˜νƒ€"), Boat("μš”νŠΈ")]
for vehicle in vehicles:
    print(vehicle.start())
    print(vehicle.move())

# λΆ€λΆ„ λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©
class ElectricCar(Car):
    def start(self):
        # λΆ€λͺ¨ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œ ν›„ ν™•μž₯
        parent_result = super().start()
        return f"{parent_result} (μ „κΈ°λͺ¨ν„° ν™œμ„±ν™”)"
    
    # move λ©”μ„œλ“œλŠ” μ˜€λ²„λΌμ΄λ”©ν•˜μ§€ μ•ŠμŒ (Car의 λ©”μ„œλ“œ μ‚¬μš©)

electric_car = ElectricCar("ν…ŒμŠ¬λΌ")
print(electric_car.start())  # ν…ŒμŠ¬λΌ 엔진을 μΌ­λ‹ˆλ‹€. (μ „κΈ°λͺ¨ν„° ν™œμ„±ν™”)
print(electric_car.move())   # ν…ŒμŠ¬λΌ λ„λ‘œλ₯Ό λ‹¬λ¦½λ‹ˆλ‹€.

βœ… νŠΉμ§•:

  • μžμ‹ ν΄λž˜μŠ€μ—μ„œ λ©”μ„œλ“œ μž¬μ •μ˜
  • λ‹€μ–‘ν•œ κ΅¬ν˜„ 제곡
  • λ™μΌν•œ μΈν„°νŽ˜μ΄μŠ€
  • λŸ°νƒ€μž„ λ‹€ν˜•μ„±
  • μ½”λ“œ μœ μ—°μ„±
  • ν™•μž₯μ„± ν–₯상
  • 뢀뢄적 μ˜€λ²„λΌμ΄λ”© κ°€λŠ₯


7️⃣ 상속과 ꡬ성

상속과 ꡬ성(Composition)은 μ½”λ“œ μž¬μ‚¬μš©μ„ μœ„ν•œ 두 κ°€μ§€ μ£Όμš” 방법이닀.

# 상속 방식 (is-a 관계)
class Engine:
    def start(self):
        return "μ—”μ§„ μ‹œλ™"
    
    def stop(self):
        return "μ—”μ§„ μ •μ§€"

class Car(Engine):  # Car is an Engine (?)
    def drive(self):
        return f"{self.start()} ν›„ μ£Όν–‰"

# ꡬ성 방식 (has-a 관계)
class BetterCar:
    def __init__(self):
        self.engine = Engine()  # Car has an Engine
    
    def drive(self):
        return f"{self.engine.start()} ν›„ μ£Όν–‰"
    
    def park(self):
        return f"μ£Όμ°¨ ν›„ {self.engine.stop()}"

# 더 λ³΅μž‘ν•œ, μœ μ—°ν•œ ꡬ성 μ˜ˆμ‹œ
class SteeringWheel:
    def turn_left(self):
        return "μ’ŒνšŒμ „"
    
    def turn_right(self):
        return "μš°νšŒμ „"

class Wheel:
    def rotate(self):
        return "바퀴 νšŒμ „"

class AdvancedCar:
    def __init__(self, engine_type):
        if engine_type == "gasoline":
            self.engine = Engine()
        elif engine_type == "electric":
            self.engine = ElectricEngine()
        self.steering = SteeringWheel()
        self.wheels = [Wheel() for _ in range(4)]
    
    def drive(self):
        return f"{self.engine.start()}, {self.wheels[0].rotate()}"
    
    def turn(self, direction):
        if direction == "left":
            return self.steering.turn_left()
        else:
            return self.steering.turn_right()

βœ… νŠΉμ§•:

  • 상속: is-a 관계 ν‘œν˜„
  • ꡬ성: has-a 관계 ν‘œν˜„
  • ꡬ성은 더 μœ μ—°ν•œ 섀계 제곡
  • ꡬ성은 λŸ°νƒ€μž„μ— κ΅¬μ„±μš”μ†Œ λ³€κ²½ κ°€λŠ₯
  • "상속보닀 ꡬ성을 μ„ ν˜Έν•˜λΌ" 원칙
  • κΈ°λŠ₯ μž¬μ‚¬μš©μ„ μœ„ν•œ λŒ€μ•ˆμ  접근법
  • μ˜μ‘΄μ„± 관리 용이


8️⃣ 닀쀑 상속 κ³ κΈ‰ 기법

닀쀑 μƒμ†μ˜ λ³΅μž‘μ„±μ„ κ΄€λ¦¬ν•˜κ³  효과적으둜 ν™œμš©ν•˜λŠ” κ³ κΈ‰ 기법이닀.

# ν˜‘λ ₯적 닀쀑 상속 (Cooperative Multiple Inheritance)
class Base:
    def __init__(self):
        print("Base.__init__")

class A(Base):
    def __init__(self):
        super().__init__()
        print("A.__init__")

class B(Base):
    def __init__(self):
        super().__init__()
        print("B.__init__")

class C(A, B):
    def __init__(self):
        super().__init__()
        print("C.__init__")

# 좜λ ₯:
# Base.__init__ (ν•œ 번만 호좜됨)
# B.__init__
# A.__init__
# C.__init__
c = C()
print(C.__mro__)

# 믹슀인과 κΈ°λ³Έ 클래슀의 μ‘°ν•©
class Serializable:
    def serialize(self):
        return str(self.__dict__)

class Loggable:
    def log(self, message):
        print(f"[LOG] {message}")

class Displayable:
    def display(self):
        print(str(self))

class GameObject(Serializable, Loggable, Displayable):
    def __init__(self, x, y, name):
        self.x = x
        self.y = y
        self.name = name
        self.log(f"Created {name} at ({x}, {y})")
    
    def __str__(self):
        return f"{self.name} at ({self.x}, {self.y})"

# 닀쀑 상속과 λͺ…λͺ…λœ νŠœν”Œ
from collections import namedtuple
from dataclasses import dataclass

Point = namedtuple('Point', ['x', 'y'])

@dataclass
class ColorPoint(Point):  # λͺ…λͺ…λœ νŠœν”Œ 상속
    color: str = "black"
    
    def __init__(self, x, y, color="black"):
        super().__init__(x, y)  # μ‹€νŒ¨! namedtuple ν™•μž₯ μ‹œ 주의

βœ… νŠΉμ§•:

  • ν˜‘λ ₯적 닀쀑 상속
  • MRO 깊이 이해
  • 믹슀인 계측 ꡬ쑰
  • 닀쀑 상속 λ””μžμΈ νŒ¨ν„΄
  • 상속 μ œν•œ 및 주의점
  • namedtuple, dataclass 상속 주의
  • μΈν„°νŽ˜μ΄μŠ€ μ‘°ν•©


μ£Όμš” 팁

βœ… λͺ¨λ²” 사둀:

  • 단일 상속 μš°μ„  μ‚¬μš©
  • 닀쀑 상속 μ‹œ MRO κ³ λ €
  • 좔상 클래슀둜 μΈν„°νŽ˜μ΄μŠ€ μ •μ˜
  • super() ν™œμš©ν•˜μ—¬ λΆ€λͺ¨ 클래슀 λ©”μ„œλ“œ 호좜
  • μ μ ˆν•œ λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©μœΌλ‘œ λ‹€ν˜•μ„± κ΅¬ν˜„
  • 믹슀인으둜 κΈ°λŠ₯ ν™•μž₯
  • νƒ€μž… 검사 ν™œμš© (isinstance, issubclass)
  • λͺ…ν™•ν•œ 계측 ꡬ쑰 섀계
  • "상속보닀 ꡬ성을 μ„ ν˜Έν•˜λΌ" 원칙 κ³ λ €
  • κΉŠμ€ 상속 계측 ν”Όν•˜κΈ° (3-4단계 μ΄ν•˜ μœ μ§€)
  • 곡톡 κΈ°λŠ₯은 μƒμœ„ 클래슀둜 이동
  • ν˜‘λ ₯적 λ©”μ„œλ“œ 호좜 νŒ¨ν„΄ μ‚¬μš©
  • 좔상화 μˆ˜μ€€ 일관성 μœ μ§€
  • λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙 μ€€μˆ˜
  • λͺ…ν™•ν•œ is-a κ΄€κ³„λ§Œ 상속 μ‚¬μš©
  • has-a κ΄€κ³„λŠ” ꡬ성 μ‚¬μš©


⚠️ **GitHub.com Fallback** ⚠️