Python 상속(Inheritance)이란 하나의 서브클래스가 다른 클래스의 코드를 재사용하는 것을 말합니다. 상속은 Python과 같은 객체 지향 언어의 핵심 기능으로, 코드 작성을 더 쉽고 효율적으로 만들어 줍니다.
Python은 객체 지향 언어입니다. 즉, 재사용 가능한 코드 블록을 만들어 코드의 효율성을 높일 수 있으며, 그 대표적인 방법 중 하나가 바로 상속입니다. 상속을 사용하면 하나의 서브클래스가 다른 클래스의 코드를 그대로 가져다 쓸 수 있습니다.
예를 들어 '은행 계좌' 정보를 저장하는 클래스가 있다고 가정해 보겠습니다. 여기서 기존 '은행 계좌' 클래스의 속성을 참조하는 '골드 은행 계좌'라는 서브클래스를 만들고 싶을 때 상속이 유용하게 사용됩니다.
이 글에서는 Python 상속의 기본 개념을 자세히 살펴보고, 부모 클래스와 자식 클래스가 어떻게 동작하는지, 그리고 클래스 객체를 다룰 때 속성과 메서드를 어떻게 오버라이드(재정의)하는지까지 단계별로 알아보겠습니다.
Python 상속이란?
상속은 한 클래스가 다른 클래스 내부에 포함되는 관계를 설명하는 용어입니다. 서브클래스(subclass) 또는 자식 클래스(child class)라고 불리는 클래스는 부모 클래스(parent class)로부터 값을 상속받을 수 있습니다. 마치 현실 세계에서 자녀가 부모로부터 특징을 물려받는 것과 같은 원리입니다.
상속이 유용한 이유는 부모 클래스와 동일한 값 타입을 가지는 서브클래스를 만들 때, 동일한 타입 선언을 여러 번 반복할 필요 없이 재사용할 수 있기 때문입니다.
부모 클래스(Parent Classes)
부모 클래스(또는 베이스 클래스(base class))는 서브클래스가 접근할 수 있는 기본 구조를 정의합니다. 부모 클래스로부터 파생된 메서드를 활용하면, 공통 값들을 반복해서 선언하지 않고도 여러 개의 서브클래스를 만들 수 있습니다.
예시를 통해 실제 동작 방식을 살펴보겠습니다. 두 가지 유형의 은행 계좌, 즉 '부모 계좌'와 '자녀 계좌'를 다루는 프로그램이 있다고 가정해 봅시다.
두 계좌 모두 은행 계좌이므로 비슷한 속성을 가집니다. 하지만 각 계좌마다 고유한 정보가 필요할 수 있습니다. 예를 들어 부모 계좌는 당좌대출(오버드래프트)을 지원하는 반면, 자녀 계좌는 잔액 범위 내에서만 거래할 수 있을 것입니다.
따라서 이러한 값을 가진 프로그램을 만들려면, 먼저 은행 계좌용 클래스(부모 클래스)를 정의한 후, 지원하려는 두 가지 계좌 유형—부모 계좌와 자녀 계좌—에 대한 자식 클래스를 생성해야 합니다.
다음은 은행 계좌의 부모 클래스를 선언하는 예제입니다:
class BankAccount: def __init__(self, forename, surname, balance): self.forename = forename self.surname = surname self.balance = balance
BankAccount 클래스를 생성했으며, 이 클래스는 이름(forename), 성(surname), 잔액(balance)이라는 세 가지 값을 저장합니다. 여기에 메서드도 추가해 보겠습니다:
class BankAccount:
def __init__(self, forename, surname, balance):
self.forename = forename
self.surname = surname
self.balance = balance
def getDetails(self):
print("Forename: ", self.forename)
print("Surname: ", self.surname)
def getBalance(self):
print("Balance: $ ", self.balance)이제 클래스는 세 가지 값과 두 개의 새로운 메서드를 저장합니다. getDetails 메서드는 예금주의 이름과 성을 확인할 때 사용하고, getBalance 메서드는 예금주의 잔액을 반환합니다.
BankAccount 클래스에서 선언한 모든 메서드와 변수는 자식 클래스에서도 접근할 수 있습니다.
클래스가 실제로 동작하는 모습을 확인하기 위해 예제 계좌를 하나 만들어 보겠습니다. 다음 코드를 사용하면 됩니다:
account = BankAccount("John", "Appleseed", 100)이 코드는 100달러를 보유한 John Appleseed의 계좌를 생성합니다. 계좌가 준비되었으니, 일반 클래스처럼 참조할 수 있습니다. 다음은 getBalance() 메서드를 실행하여 잔액을 확인하는 예제입니다:
print(account.getBalance())
프로그램은 다음과 같이 출력됩니다:
Balance: $ 100
자식 클래스(Child Classes)
앞선 예제에서는 은행 계좌 정보를 저장하는 부모 클래스를 선언했습니다. 그렇다면 18세 미만 자녀용 계좌처럼 특정 유형의 계좌 정보를 저장하고 싶다면 어떻게 해야 할까요? 바로 이럴 때 자식 클래스가 등장합니다.
자식 클래스, 즉 서브클래스는 부모 클래스로부터 값을 상속받습니다. 따라서 각 자식 클래스는 부모 클래스에서 선언한 메서드와 변수를 참조할 수 있습니다.
ChildBankAccount라는 자식 클래스를 만들어 보겠습니다. 이 계좌는 사용자의 은행 계좌 정보(이름과 잔액)를 반환할 수 있어야 하며, restricted라는 새로운 값도 가져야 합니다. 18세 미만 자녀는 완전한 은행 계좌를 개설할 자격이 없기 때문에, 이 값이 자녀 계좌에 저장되는 것입니다.
자식 클래스를 생성하려면 다음 코드를 사용합니다:
class ChildBankAccount(BankAccount):
BankAccount가 괄호 안에 포함되어 있기 때문에 ChildBankAccount 클래스는 BankAccount 클래스의 자식이 됩니다. 만약 자식 클래스가 부모 클래스인 BankAccount와 정확히 동일한 값을 가지기를 원한다면, pass 키워드를 다음과 같이 사용할 수 있습니다:
class ChildBankAccount(BankAccount): pass
하지만 앞서 논의했듯이, 우리의 자녀 계좌는 특수한 값을 하나 더 저장해야 합니다. 바로 사용자의 계좌가 제한되어 있음을 나타내는 변수입니다.
이 목표를 달성하기 위해 자식 클래스에 restricted라는 새로운 값을 추가해 보겠습니다. 다음 코드를 사용하면 됩니다:
class ChildBankAccount(BankAccount):
def __init__(self, forename, surname, balance, restricted=True):
self.forename = forename
self.surname = surname
self.balance = balance
self.restricted = restricted
def isRestricted(self):
print("This account is restricted as the user is under 18.")이제 코드를 하나씩 살펴보겠습니다.
위 코드에서는 ChildBankAccount가 네 가지 값, 즉 이름(forename), 성(surname), 잔액(balance), 제한 여부(restricted)를 저장하도록 정의했습니다.
처음 세 가지 값—이름, 성, 잔액—은 부모 클래스의 값과 동일하지만, restricted는 새로운 값으로 자식 클래스에만 존재합니다. 기본적으로 restricted는 True로 설정됩니다.
따라서 일반 BankAccount(ChildBankAccount가 아닌)를 생성하면 restricted 값에 접근할 수 없습니다. 마찬가지로 isRestricted 메서드도 ChildBankAccount를 생성해야만 사용할 수 있습니다.
다음은 ChildBankAccount를 생성하고 getBalance()와 isRestricted() 메서드를 사용하여 클래스 정보를 확인하는 예제입니다:
child_account = ChildBankAccount("Bill", "Appleseed", 100)
print(child_account.getBalance())
print(child_account.isRestricted())코드 실행 결과는 다음과 같습니다:
Balance: $ 100 This account is restricted as the user is under 18.
실행 결과를 보면, 프로그램은 먼저 100달러를 보유한 Bill Appleseed의 새로운 ChildBankAccount를 정의합니다. 그런 다음 클래스의 getBalance() 메서드를 실행하여 사용자의 잔액을 반환합니다. 여기서 주목할 점은 getBalance()가 부모 클래스인 BankAccount에 선언되어 있지만, 상속을 통해 ChildBankAccount에서도 접근 가능하다는 것입니다.
프로그램은 또한 isRestricted() 메서드를 실행하여, 사용자가 18세 미만이기 때문에 계좌가 제한되어 있다는 메시지를 출력합니다. 이 메서드는 ChildBankAccount에 선언되어 있으므로 부모 클래스인 BankAccount에서는 접근할 수 없습니다. 만약 제한 기능이 있는 일반 BankAccount를 만들고 싶다면 부모 클래스 자체를 수정해야 합니다.
부모 메서드 재정의하기
앞선 예제에서는 은행 계좌 정보를 저장하는 BankAccount라는 부모 클래스를 선언하고, 18세 미만 예금주 정보를 저장하는 ChildBankAccount라는 자식 클래스를 만들었습니다. 이 자식 클래스는 BankAccount 클래스의 메서드를 상속받았으며, 추가로 isRestricted() 메서드도 생성했습니다.
그렇다면 기존 부모 클래스의 메서드를 수정하고 싶다면 어떻게 해야 할까요? 예를 들어 BankAccount의 잔액 조회 시 잔액과 함께 '당좌대출 신청 자격이 있습니다'라는 메시지를 출력하고 싶지만, ChildBankAccount 예금주에게는 이 메시지가 표시되지 않기를 원한다고 가정해 봅시다.
이를 위해서는 부모 메서드를 오버라이드(재정의)해야 합니다.
다음은 수정된 부모 메서드 BankAccount의 코드입니다. 이제 예금주가 잔액을 조회하면 당좌대출 자격이 있음을 알리는 메시지가 함께 출력됩니다:
class BankAccount:
def __init__(self, forename, surname, balance):
self.forename = forename
self.surname = surname
self.balance = balance
def getDetails(self):
print("Forename: ", self.forename)
print("Surname: ", self.surname)
def getBalance(self):
print("Balance: $ ", self.balance)
print("You are eligible for an overdraft.")이제 John Appleseed를 위해 새로운 BankAccount를 생성하고 잔액을 출력하면, '당좌대출 신청 자격이 있습니다'라는 메시지가 함께 표시됩니다. 새 계좌를 선언하고 잔액을 확인하는 예제는 다음과 같습니다:
account = BankAccount("John", "Appleseed", 100)
print(account.getBalance())코드 실행 결과는 다음과 같습니다:
Balance: $ 100 You are eligible for an overdraft.
그런데 이 변경으로 인해 BankAccount로부터 값을 상속받는 ChildBankAccount에서도 동일한 메시지가 표시됩니다. 부모 클래스가 변경된 상태에서 ChildBankAccount를 생성하고 잔액을 확인해 보겠습니다:
child_account = ChildBankAccount("Bill", "Appleseed", 100)
print(child_account.getBalance())코드 실행 결과:
Balance: $ 100 You are eligible for an overdraft.
일반적으로 자녀에게는 당좌대출이 허용되지 않으므로, 코드를 수정해야 합니다. 이를 위해서는 ChildBankAccount 클래스를 수정하고 새로운 getBalance() 함수를 선언해야 합니다.
먼저 앞서 작성한 ChildBankAccount 코드는 다음과 같습니다:
class ChildBankAccount(BankAccount):
def __init__(self, forename, surname, balance, restricted=True):
self.forename = forename
self.surname = surname
self.balance = balance
self.restricted = restricted
def isRestricted(self):
print("This account is restricted as the user is under 18.")여기에 새로운 함수 getBalance()를 추가해야 합니다. 이 함수는 부모 클래스에서 선언한 것과 동일하게 동작하지만, 당좌대출 관련 메시지는 포함하지 않습니다. 새 함수를 선언하기 위해 추가하는 코드는 다음과 같습니다:
…
def getBalance(self):
print("Balance: $ ", self.balance)이제 자녀 계좌의 잔액을 다시 조회해 보겠습니다:
child_account = ChildBankAccount("Bill", "Appleseed", 100)
print(child_account.getBalance())코드 실행 결과는 다음과 같습니다:
Balance: $ 100
위 예제에서 우리는 ChildBankAccount 자식 클래스의 getBalance() 메서드를 오버라이드했습니다. ChildBankAccount의 새로운 getBalance() 메서드는 사용자의 잔액만 표시합니다. 반면 BankAccount 클래스의 getBalance() 메서드는 사용자의 잔액과 함께 '당좌대출 신청 자격이 있습니다'라는 메시지를 모두 표시합니다.
부모 메서드 오버라이딩은 여러 자식 클래스가 부모와 유사한 메서드를 사용하면서도 각자 고유한 변경 사항이 필요할 때 매우 유용합니다. 위 예제에서처럼 부모 은행 계좌에는 당좌대출 메시지를 보여주되, 자녀 은행 계좌에는 보여주지 않기 위해 ChildBankAccount에서 부모 메서드를 오버라이드한 것입니다.
마무리
상속은 객체 지향 프로그래밍에서 부모 클래스에 이미 선언된 값을 저장할 수 있는 서브클래스를 만들 때 활용됩니다. 비슷한 값을 저장하는 유사한 클래스들을 만들 때 상속을 사용하면 코드를 여러 번 반복 작성하지 않고도 클래스를 효율적으로 생성할 수 있습니다.
이 글에서는 Python에서 상속의 역할을 살펴보고, 상속과 부모·자식 클래스가 동작하는 방식을 알아본 뒤, 부모 메서드를 오버라이드하는 방법까지 살펴보았습니다. 이제 여러분은 Python 전문가처럼 클래스와 상속을 자유롭게 활용할 준비가 되었습니다!