본문 바로가기

Python

[Python] 6. 클래스 (2) - 클래스 심화 (상속, 메서드 오버라이딩, 추상)

반응형

 

[목차]

 

1. 클래스 상속(Inheritance)

 

2. 메서드 오버라이딩(Method Overriding)

 

3. 추상 클래스(Abstract Class)

 

 

  1. 클래스 상속(Inheritance)

 

1. 클래스 상속 기본

 

무언가를 물려받는다는 '상속'의 의미에 맞게, 클래스 상속은 어떤 클래스의 기능을 그대로 물려받으면서 다른 기능을 더 추가할 수 있는 기능을 말한다. 

보통 상속을 해주는 클래스기반 클래스(base class) 또는 부모 클래스(parent class)라고 하고, 상속을 받는 클래스파생 클래스(derived class) 또는 자식 클래스(child class)라고 한다. 

상속 기능이 유용한 경우는 새로운 기능이 필요할 때마다 계속해서 클래스를 새로 만드는 비효율적인 일을 방지하기 위함이다. 기존의 기능을 재사용할 수 있다는 장점이 있다. 

 

클래스를 상속받는 방법은 클래스를 만들 때 이름 뒤에 괄호 ( )를 쓰고, 괄호 안에 상속을 받는 부모 클래스의 이름을 적어주면 된다. 

 

class 자식 클래스 명(부모 클래스 명):

    코드

 

아래의 예시를 한번 보자. 

class Family:
    lastname = "홍"
    def lname(self):
        print("성은 %s입니다." %self.lastname)

class Person(Family):
    firstname = "길동"
    def fname(self):
        print("이름은 %s입니다." %self.firstname)

a = Family()
b = Person()

a.lname() # 성은 홍입니다.
b.fname() # 이름은 길동입니다.
b.lname() # 성은 홍입니다.
a.fname() # AttributeError: 'Family' object has no attribute 'fname'

 

위 코드는 어느 가족의 성과 이름을 출력하는 간단한 클래스이다. 

가족의 성이 모두 같다고 가정한다면 굳이 각 구성원마다 성을 출력해주는 함수를 넣어줄 필요 없이, Family라는 부모 클래스를 상속받아서 Family의 메서드를 그대로 이용하면 된다. 

위 코드에서는 Family가 부모 클래스, Person이 자식 클래스가 된다. 

부모 클래스는 자식 클래스의 메서드를 상속받지 않으므로 a.fname()를 수행하면 에러가 발생한다

 

 

2. 부모 클래스의 속성 이용하기

 

이번에는 클래스 변수를 생성자 함수를 이용해서 선언해보자. 

class Family:
    def __init__(self):
        self.lastname = "홍"

    def lname(self):
        print("성은 %s입니다." %self.lastname)

class Person(Family):
    def __init__(self):
        self.firstname = "길동"

    def fname(self):
        print("이름은 %s입니다." %self.firstname)

a = Family()
b = Person()

print(a.lastname) # 홍
print(b.lastname) # AttributeError: 'Person' object has no attribute 'lastname'

 

Person 클래스의 인스턴스에서 lastname 속성을 접근하려고 하면 에러가 발생한다. 객체 b를 생성할 땐, Person의 생성자 함수만 호출되기 때문이다. 

따라서, 부모 클래스의 속성을 생성하고 초기화시켜주기 위해서는 Family 클래스의 __init__ 메서드를 호출해야 하는데, 이는 super()라는 함수를 이용해주어야 한다. 

 

자식 클래스의 __init__ 메서드 내에서 super().__init__()의 형태로 호출하면 부모 클래스의 __init__ 메서드를 호출할 수 있다. 

class Family:
    def __init__(self):
        self.lastname = "홍"
    def lname(self):
        print("성은 %s입니다." %self.lastname)

class Person(Family):
    def __init__(self):
        self.firstname = "길동"
        super().__init__()

    def fname(self):
        print("이름은 %s입니다." %self.firstname)

a = Family()
b = Person()

print(a.lastname) # 홍
print(b.lastname) # 홍

 

super() 함수를 조금 더 명확하게 사용하기 위해, super(자식 클래스 명, self)로도 사용할 수 있다. 

"'자식 클래스 명' 클래스의 super 클래스의 __init__ 메서드를 호출한다"와 같이 조금 더 명확하게 확인할 수 있다.  

 

만약 자식 클래스에 __init__ 메서드가 생략되었다면 자동으로 부모 클래스의 __init__ 메서드가 호출된다. 

요약하면, 자식 클래스에 __init__ 메서드가 있다면 이를 호출하고, 그렇지 않으면 부모 클래스의 __init__ 메서드를 호출한다.  

class Family:
    def __init__(self):
        self.lastname = "홍"
    def lname(self):
        print("성은 %s입니다." %self.lastname)

class Person(Family):
    def fname(self):
        print("이름은 %s입니다." %self.firstname)

a = Family()
b = Person()

print(a.lastname) # 홍
print(b.lastname) # 홍

 

 

  2. 메서드 오버라이딩(Method Overriding)

 

메서드 오버라이딩(Method Overriding)이란, 부모 클래스에 선언된 메서드와 동일한 이름의 메서드를 자식 클래스에도 선언하여, 메서드를 덮어 씌우는 기능을 말한다. '재정의한다'라고도 말한다. 

 

부모 클래스의 메서드를 그대로 이용하기 위해서 코드를 모두 작성했다가, 특정 클래스에만 메서드에 다른 기능을 추가하고 싶은 경우가 생길 수 있다. 

이때, 만약 새로운 메서드를 생성한다면 작성해둔 코드에서 메서드 명을 모두 바꿔주는 불편함이 있다. 

이런 경우에 메서드 오버라이딩을 이용하면 동일한 메서드 명을 사용하면서 기능을 변경하거나 추가할 수 있다. 

class Family:
    def introduce(self):
        print("저희는 가족입니다.")

class Person(Family):
    pass

a = Family()
b = Person()

a.introduce() # 저희는 가족입니다.
b.introduce() # 저희는 가족입니다.

 

기본적으로 부모 클래스에 생성된 메서드를 자식 클래스에서도 호출할 수 있다. 

하지만, 자식 클래스에 동일한 이름의 메서드를 생성하면 부모 클래스의 메서드는 무시되고 자식 클래스의 메서드가 호출된다. 

class Family:
    def introduce(self):
        print("저희는 가족입니다.")

class Person(Family):
    def introduce(self):
        print("저는 가족의 구성원입니다.")

a = Family()
b = Person()

a.introduce() # 저희는 가족입니다.
b.introduce() # 저는 가족의 구성원입니다.

 

만약 메서드 오버라이딩을 한 후에, 부모 클래스의 메서드를 호출하고 싶다면 마찬가지로 super() 함수를 이용한다. 원래 기능을 유지하면서 새로운 기능을 추가할 수 있다. 

class Family:
    def introduce(self):
        print("저희는 가족입니다.")

class Person(Family):
    def introduce(self):
        super().introduce()
        print("저는 가족의 구성원입니다.")

a = Family()
b = Person()

a.introduce() # 저희는 가족입니다.
b.introduce() # 저희는 가족입니다. 
              # 저는 가족의 구성원입니다.

 

 

  3. 추상 클래스(Abstract Class)

 

추상 클래스(Abstract Class)란, 구현되지 않은 추상 메서드를 하나 이상 가지고 있는 클래스를 말한다.

추상 메서드는 반드시 자식 클래스에서 재정의를 해주어야 한다. 

그렇다면 굳이 이렇게 재정의하는 방식으로 사용하는 이유가 뭘까?

 

부모 클래스를 만들 때, 자식 클래스에 특정 메서드가 꼭 존재해야 하거나 자식 클래스마다 이름은 같지만 기능은 모두 다른 메서드를 만들고 싶은 경우에 이를 구현하도록 강제하기 위해서 사용한다. 

추상 메서드를 자식 클래스에서 구현하지 않으면 객체를 생성할 때 에러가 발생하기 때문이다.

 

간단하게 말하면, 자식 클래스에서 구현해야 할 메서드의 기본 틀을 만들어 놓은 것이다. 

 

추상 클래스를 생성하기 위해서는 abc 모듈을 import 해주어야 한다. 그리고 추상 클래스는 클래스의 괄호 안에 metaclass=ABCMeta로 지정하고, 추상 메서드는 함수 위에 @abstractmethod라는 키워드를 적어주어 다른 메서드들과 구별한다. 

 

아래의 코드는 추상 클래스의 기본 틀이다.

from abc import *

class Family(metaclass=ABCMeta):

    @abstractmethod
    def introduce(self):
        pass

 

만약 자식 클래스에서 재정의를 하지 않고 객체를 생성하면 어떻게 될까?

from abc import *

class Family(metaclass=ABCMeta):

    @abstractmethod
    def introduce(self):
        pass


class Person(Family):
    pass

a = Person() # TypeError: Can't instantiate abstract class Person with abstract methods introduce

 

이처럼 자식 클래스인 Person에서 introduce 메서드를 재정의하지 않으면 에러가 발생한다. 

from abc import *

class Family(metaclass=ABCMeta):
    @abstractmethod
    def introduce(self):
        pass


class Person(Family):
    def introduce(self):
        print("저는 사람입니다.")

a = Person()
a.introduce() # 저는 사람입니다.

 

추가적으로, 추상 클래스로는 인스턴스를 생성할 수 없다. 따라서 추상 메서드도 호출할 일이 없으므로 추상 메서드는 주로 pass로 비워둔다. 

 

PC로 보시는 것을 권장합니다. 

피드백은 언제나 환영입니다. 댓글로 달아주세요 ^-^

 

 

반응형