상속의 동작

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Python 에서의 상속 동작에 대해 배우게 됩니다. 특히, super() 함수의 작동 방식과 협력적 상속 (cooperative inheritance) 이 어떻게 구현되는지에 초점을 맞출 것입니다. 상속은 객체 지향 프로그래밍의 기본적인 개념으로, 클래스가 코드 재사용 및 계층적 클래스 구조를 위해 부모 클래스에서 속성과 메서드를 상속받을 수 있도록 합니다.

이 실습을 통해 Python 에서 단일 상속 및 다중 상속을 포함한 다양한 유형의 상속을 이해하게 될 것입니다. 또한 super() 함수를 사용하여 상속 계층 구조를 탐색하고, 협력적 다중 상속의 실용적인 예시를 구현하며, 이러한 개념을 적용하여 유효성 검사 시스템을 구축하는 방법을 배우게 됩니다. 이 랩에서 생성되는 주요 파일은 validate.py입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 중급 레벨의 실험이며 완료율은 69%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

단일 상속과 다중 상속 이해하기

이 단계에서는 Python 의 두 가지 주요 상속 유형인 단일 상속과 다중 상속에 대해 배우겠습니다. 상속은 객체 지향 프로그래밍의 기본 개념으로, 클래스가 다른 클래스에서 속성과 메서드를 상속받을 수 있도록 합니다. 또한 여러 후보가 있을 때 Python 이 어떤 메서드를 호출할지 결정하는 방법, 즉 메서드 결정 (method resolution) 이라고 하는 프로세스도 살펴보겠습니다.

단일 상속

단일 상속은 클래스가 단일 조상 계열을 형성하는 경우입니다. 각 클래스가 하나의 직접적인 부모만 있는 가계도와 같습니다. 작동 방식을 이해하기 위해 예시를 만들어 보겠습니다.

먼저 WebIDE 에서 새 터미널을 엽니다. 터미널이 열리면 다음 명령을 입력하고 Enter 키를 눌러 Python 인터프리터를 시작합니다.

python3

이제 Python 인터프리터에 들어갔으므로 단일 상속 체인을 형성하는 세 개의 클래스를 만들겠습니다. 다음 코드를 입력합니다.

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()

class C(B):
    def spam(self):
        print('C.spam')
        super().spam()

이 코드에서 클래스 B는 클래스 A를 상속하고, 클래스 C는 클래스 B를 상속합니다. super() 함수는 부모 클래스의 메서드를 호출하는 데 사용됩니다.

이러한 클래스를 정의한 후 Python 이 메서드를 검색하는 순서를 알 수 있습니다. 이 순서를 메서드 결정 순서 (Method Resolution Order, MRO) 라고 합니다. 클래스 C의 MRO 를 보려면 다음 코드를 입력합니다.

C.__mro__

다음과 유사한 출력을 볼 수 있습니다.

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

이 출력은 Python 이 먼저 클래스 C에서 메서드를 찾고, 다음으로 클래스 B에서, 그 다음으로 클래스 A에서, 마지막으로 기본 object 클래스에서 메서드를 찾는다는 것을 보여줍니다.

이제 클래스 C의 인스턴스를 생성하고 spam() 메서드를 호출해 보겠습니다. 다음 코드를 입력합니다.

c = C()
c.spam()

다음 출력을 볼 수 있습니다.

C.spam
B.spam
A.spam

이 출력은 단일 상속 체인에서 super()가 어떻게 작동하는지 보여줍니다. C.spam()super().spam()을 호출하면 B.spam()을 호출합니다. 그런 다음 B.spam()super().spam()을 호출하면 A.spam()을 호출합니다.

다중 상속

다중 상속을 사용하면 클래스가 둘 이상의 부모 클래스에서 상속받을 수 있습니다. 이를 통해 클래스는 모든 부모 클래스의 속성과 메서드에 액세스할 수 있습니다. 이 경우 메서드 결정이 어떻게 작동하는지 살펴보겠습니다.

Python 인터프리터에 다음 코드를 입력합니다.

class Base:
    def spam(self):
        print('Base.spam')

class X(Base):
    def spam(self):
        print('X.spam')
        super().spam()

class Y(Base):
    def spam(self):
        print('Y.spam')
        super().spam()

class Z(Base):
    def spam(self):
        print('Z.spam')
        super().spam()

이제 여러 부모 클래스 X, Y, Z에서 상속하는 클래스 M을 만들겠습니다. 다음 코드를 입력합니다.

class M(X, Y, Z):
    pass

M.__mro__

다음 출력을 볼 수 있습니다.

(<class '__main__.M'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.Base'>, <class 'object'>)

이 출력은 클래스 M의 메서드 결정 순서를 보여줍니다. Python 은 이 순서대로 메서드를 검색합니다.

클래스 M의 인스턴스를 생성하고 spam() 메서드를 호출해 보겠습니다.

m = M()
m.spam()

다음 출력을 볼 수 있습니다.

X.spam
Y.spam
Z.spam
Base.spam

super()가 바로 상위 부모 클래스의 메서드만 호출하는 것이 아님을 알 수 있습니다. 대신, 자식 클래스에 의해 정의된 메서드 결정 순서 (MRO) 를 따릅니다.

다른 순서로 부모 클래스를 가진 다른 클래스 N을 만들어 보겠습니다.

class N(Z, Y, X):
    pass

N.__mro__

다음 출력을 볼 수 있습니다.

(<class '__main__.N'>, <class '__main__.Z'>, <class '__main__.Y'>, <class '__main__.X'>, <class '__main__.Base'>, <class 'object'>)

이제 클래스 N의 인스턴스를 생성하고 spam() 메서드를 호출합니다.

n = N()
n.spam()

다음 출력을 볼 수 있습니다.

Z.spam
Y.spam
X.spam
Base.spam

이것은 중요한 개념을 보여줍니다. Python 의 다중 상속에서 클래스 정의의 부모 클래스 순서가 메서드 결정 순서를 결정합니다. super() 함수는 호출되는 클래스에 관계없이 이 순서를 따릅니다.

이러한 개념을 모두 탐색했으면 다음 코드를 입력하여 Python 인터프리터를 종료할 수 있습니다.

exit()

상속을 사용한 유효성 검사 시스템 구축

이 단계에서는 상속을 사용하여 실용적인 유효성 검사 시스템을 구축할 것입니다. 상속은 프로그래밍에서 기존 클래스를 기반으로 새로운 클래스를 만들 수 있게 해주는 강력한 개념입니다. 이러한 방식으로 코드를 재사용하고 더 체계적이고 모듈화된 프로그램을 만들 수 있습니다. 이 유효성 검사 시스템을 구축함으로써 상속이 다양한 방식으로 결합될 수 있는 재사용 가능한 코드 구성 요소를 만드는 데 어떻게 사용될 수 있는지 알 수 있습니다.

기본 검사기 클래스 생성

먼저, 검사기를 위한 기본 클래스를 만들어야 합니다. 이를 위해 WebIDE 에서 새 파일을 만들 것입니다. 방법은 다음과 같습니다. "File" > "New File"을 클릭하거나 키보드 단축키를 사용할 수 있습니다. 새 파일이 열리면 이름을 validate.py로 지정합니다.

이제 이 파일에 코드를 추가하여 기본 Validator 클래스를 만들어 보겠습니다. 이 클래스는 다른 모든 검사기의 기반 역할을 합니다.

## validate.py
class Validator:
    @classmethod
    def check(cls, value):
        return value

이 코드에서는 check 메서드가 있는 Validator 클래스를 정의했습니다. check 메서드는 값을 인수로 받아 변경 없이 그대로 반환합니다. @classmethod 데코레이터는 이 메서드를 클래스 메서드로 만듭니다. 즉, 클래스의 인스턴스를 만들 필요 없이 클래스 자체에서 이 메서드를 호출할 수 있습니다.

유형 검사기 추가

다음으로, 값의 유형을 확인하는 몇 가지 검사기를 추가하겠습니다. 이러한 검사기는 방금 만든 Validator 클래스에서 상속됩니다. validate.py 파일로 돌아가서 다음 코드를 추가합니다.

class Typed(Validator):
    expected_type = object
    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        return super().check(value)

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

Typed 클래스는 Validator의 서브클래스입니다. expected_type 속성이 있으며, 초기에는 object로 설정됩니다. Typed 클래스의 check 메서드는 주어진 값이 예상 유형인지 확인합니다. 그렇지 않은 경우 TypeError를 발생시킵니다. 유형이 올바르면 super().check(value)를 사용하여 부모 클래스의 check 메서드를 호출합니다.

Integer, Float, String 클래스는 Typed에서 상속받아 확인해야 하는 정확한 유형을 지정합니다. 예를 들어, Integer 클래스는 값이 정수인지 확인합니다.

유형 검사기 테스트

이제 유형 검사기를 만들었으므로 테스트해 보겠습니다. 새 터미널을 열고 다음 명령을 실행하여 Python 인터프리터를 시작합니다.

python3

Python 인터프리터가 실행되면 검사기를 가져와 테스트할 수 있습니다. 다음은 이를 테스트하기 위한 코드입니다.

from validate import Integer, String

Integer.check(10)  ## Should return 10

try:
    Integer.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

String.check('10')  ## Should return '10'

이 코드를 실행하면 다음과 같은 내용이 표시됩니다.

10
Error: Expected <class 'int'>
'10'

이러한 검사기를 함수에서도 사용할 수 있습니다. 시도해 보겠습니다.

def add(x, y):
    Integer.check(x)
    Integer.check(y)
    return x + y

add(2, 2)  ## Should return 4

try:
    add('2', '3')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

이 코드를 실행하면 다음이 표시됩니다.

4
Error: Expected <class 'int'>

값 검사기 추가

지금까지 값의 유형을 확인하는 검사기를 만들었습니다. 이제 유형이 아닌 값 자체를 확인하는 몇 가지 검사기를 추가해 보겠습니다. validate.py 파일로 돌아가서 다음 코드를 추가합니다.

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        return super().check(value)

class NonEmpty(Validator):
    @classmethod
    def check(cls, value):
        if len(value) == 0:
            raise ValueError('Must be non-empty')
        return super().check(value)

Positive 검사기는 값이 음수가 아닌지 확인합니다. 값이 0 보다 작으면 ValueError를 발생시킵니다. NonEmpty 검사기는 값의 길이가 0 이 아닌지 확인합니다. 길이가 0 이면 ValueError를 발생시킵니다.

다중 상속을 사용한 검사기 구성

이제 다중 상속을 사용하여 검사기를 결합할 것입니다. 다중 상속을 사용하면 클래스가 둘 이상의 부모 클래스에서 상속받을 수 있습니다. validate.py 파일로 돌아가서 다음 코드를 추가합니다.

class PositiveInteger(Integer, Positive):
    pass

class PositiveFloat(Float, Positive):
    pass

class NonEmptyString(String, NonEmpty):
    pass

이러한 새 클래스는 유형 검사와 값 검사를 결합합니다. 예를 들어, PositiveInteger 클래스는 값이 정수이고 음수가 아닌지 확인합니다. 여기서 상속 순서가 중요합니다. 검사기는 클래스 정의에 지정된 순서대로 확인됩니다.

구성된 검사기 테스트

구성된 검사기를 테스트해 보겠습니다. Python 인터프리터에서 다음 코드를 실행합니다.

from validate import PositiveInteger, PositiveFloat, NonEmptyString

PositiveInteger.check(10)  ## Should return 10

try:
    PositiveInteger.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

try:
    PositiveInteger.check(-10)  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

NonEmptyString.check('hello')  ## Should return 'hello'

try:
    NonEmptyString.check('')  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

이 코드를 실행하면 다음이 표시됩니다.

10
Error: Expected <class 'int'>
Error: Expected >= 0
'hello'
Error: Must be non-empty

이것은 검사기를 결합하여 더 복잡한 유효성 검사 규칙을 만들 수 있는 방법을 보여줍니다.

테스트를 마쳤으면 다음 명령을 실행하여 Python 인터프리터를 종료할 수 있습니다.

exit()

주식 클래스에 검사기 적용하기

이 단계에서는 실제 상황에서 검사기가 어떻게 작동하는지 살펴보겠습니다. 검사기는 우리가 사용하는 데이터가 특정 규칙을 충족하는지 확인하는 작은 검사기와 같습니다. Stock 클래스를 만들 것입니다. 클래스는 객체를 생성하기 위한 청사진과 같습니다. 이 경우 Stock 클래스는 주식 시장의 주식을 나타내며, 속성 (예: 주식 수 및 가격) 의 값이 유효한지 확인하기 위해 검사기를 사용합니다.

Stock 클래스 생성

먼저, 새 파일을 만들어야 합니다. WebIDE 에서 stock.py라는 새 파일을 만듭니다. 이 파일에는 Stock 클래스에 대한 코드가 포함됩니다. 이제 다음 코드를 stock.py 파일에 추가합니다.

## stock.py
from validate import PositiveInteger, PositiveFloat

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        self._shares = PositiveInteger.check(value)

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        self._price = PositiveFloat.check(value)

    def cost(self):
        return self.shares * self.price

이 코드가 수행하는 작업을 자세히 살펴보겠습니다.

  1. 먼저 validate 모듈에서 PositiveIntegerPositiveFloat 검사기를 가져옵니다. 이러한 검사기는 주식 수가 양의 정수이고 가격이 양의 부동 소수점인지 확인하는 데 도움이 됩니다.
  2. 그런 다음 Stock 클래스를 정의합니다. 클래스 내부에 __init__ 메서드가 있습니다. 이 메서드는 새 Stock 객체를 만들 때 호출됩니다. name, shares, price의 세 가지 매개변수를 사용하고 이를 객체의 속성에 할당합니다.
  3. 속성 (property) 과 setter 를 사용하여 sharesprice의 값을 검사합니다. 속성은 속성에 대한 액세스를 제어하는 방법이며, setter 는 해당 속성의 값을 설정하려고 할 때 호출되는 메서드입니다. shares 속성을 설정하면 PositiveInteger.check 메서드가 호출되어 값이 양의 정수인지 확인합니다. 마찬가지로 price 속성을 설정하면 PositiveFloat.check 메서드가 호출되어 값이 양의 부동 소수점인지 확인합니다.
  4. 마지막으로 cost 메서드가 있습니다. 이 메서드는 주식 수에 가격을 곱하여 주식의 총 비용을 계산합니다.

Stock 클래스 테스트

이제 Stock 클래스를 만들었으므로 검사기가 제대로 작동하는지 테스트해야 합니다. 새 터미널을 열고 Python 인터프리터를 시작합니다. 다음 명령을 실행하여 이 작업을 수행할 수 있습니다.

python3

Python 인터프리터가 실행되면 Stock 클래스를 가져와 테스트할 수 있습니다. 다음 코드를 Python 인터프리터에 입력합니다.

from stock import Stock

## Create a valid stock
s = Stock('GOOG', 100, 490.10)
print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
print(f"Cost: {s.cost()}")

## Try setting an invalid shares value
try:
    s.shares = -10
except ValueError as e:
    print(f"Error setting shares: {e}")

## Try setting an invalid price value
try:
    s.price = "not a price"
except TypeError as e:
    print(f"Error setting price: {e}")

이 코드를 실행하면 다음과 유사한 출력이 표시됩니다.

Name: GOOG, Shares: 100, Price: 490.1
Cost: 49010.0
Error setting shares: Expected >= 0
Error setting price: Expected <class 'float'>

이 출력은 검사기가 예상대로 작동함을 보여줍니다. Stock 클래스는 sharesprice에 대해 유효하지 않은 값을 설정하는 것을 허용하지 않습니다. 유효하지 않은 값을 설정하려고 하면 오류가 발생하며, 해당 오류를 catch 하여 출력할 수 있습니다.

상속이 어떻게 도움이 되는지 이해하기

검사기를 사용하는 것의 가장 좋은 점 중 하나는 다양한 유효성 검사 규칙을 쉽게 결합할 수 있다는 것입니다. 상속은 Python 에서 기존 클래스를 기반으로 새로운 클래스를 만들 수 있게 해주는 강력한 개념입니다. 다중 상속을 사용하면 super() 함수를 사용하여 여러 부모 클래스의 메서드를 호출할 수 있습니다.

예를 들어, 주식 이름이 비어 있지 않은지 확인하려면 다음 단계를 따를 수 있습니다.

  1. validate 모듈에서 NonEmptyString 검사기를 가져옵니다. 이 검사기는 주식 이름이 빈 문자열이 아닌지 확인하는 데 도움이 됩니다.
  2. Stock 클래스에서 name 속성에 대한 속성 setter 를 추가합니다. 이 setter 는 NonEmptyString.check() 메서드를 사용하여 주식 이름을 검사합니다.

이것은 상속, 특히 super() 함수를 사용한 다중 상속을 통해 유연하고 다양한 조합으로 재사용할 수 있는 구성 요소를 구축할 수 있음을 보여줍니다.

테스트를 마쳤으면 다음 명령을 실행하여 Python 인터프리터를 종료할 수 있습니다.

exit()

요약

이 랩에서는 Python 에서 상속의 동작에 대해 배우고 몇 가지 핵심 개념을 이해했습니다. 단일 상속과 다중 상속의 차이점을 살펴보고, super() 함수가 Method Resolution Order (MRO) 를 탐색하는 방식을 이해했으며, 협력적 다중 상속을 구현하는 방법을 배우고, 상속을 적용하여 실용적인 유효성 검사 시스템을 구축했습니다.

또한 상속을 사용하여 유연한 유효성 검사 프레임워크를 만들고 Stock 클래스를 사용하여 실제 예제에 적용하여 상속이 재사용 가능하고 구성 가능한 구성 요소를 생성할 수 있음을 보여주었습니다. 주요 내용은 super()가 단일 및 다중 상속에서 작동하는 방식, 다중 상속이 기능을 구성하는 능력, 검사기와 함께 속성 setter 를 사용하는 방법을 포함합니다. 이러한 개념은 Python 의 객체 지향 프로그래밍의 기본이며 실제 응용 프로그램에서 널리 사용됩니다.