ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL.34 클래스 상속(오버라이딩 및 추상클래스)
    TIL 2020. 11. 11. 15:37
    728x90

    ## 클래스상속하기_2

    ## 메서드 오버라이딩(method overriding)

    ## 메서드 오버라이딩 == 파생 클래스 안에서 기반 클래스의 메서드를 새로 정의하는 방법이다.

    class Person:
        def greeting(self):
        print('안녕하세요.')
    
    class Student(Person):
        def greeting(self):
        print('안녕하세요. 저는 파이썬 코딩 도장 학생입니다.')
    
    
    james = Student()
    
    james.greeting()
    
    # '안녕하세요. 저는 파이썬 코딩 도장 학생입니다.'

    # 안녕하세요가 아닌 안녕하세요. 저는 파이썬 코딩 도장 학생입니다가 출력되었다.

    ## 오버라이딩(overriding)은 무시하다, 우선하다라는 뜻을 가지고 있는데

         말 그대로 기반 클래스의 메서드를 무시하고 새로운 메서드를 만든다는 뜻이다.

    ## 여기서는 Person 클래스의 greeting 메서드를 무시하고 Student 클래스에서 새로운 greeting 메서드를 만들었다.

    ## 별도의 코드는 없으며 위와 같이 작성하는 방법 자체가 오버라이딩이다.


    ## 그럼 메서드 오버라이딩은 왜 사용할까?

    ## 보통 프로그램에서 어떤 기능 같은 메서드 이름으로 계속 사용되어야 할 때 메서드 오버라이딩을 활용하는데

    ## 만약 Student 클래스에서 인사하는 메서드를 greeting2로 만들어야 한다면

    ## 모든 소스 코드에서 메서드 호출 부분을 greeting2로 수정해야하는 번거로움이 있다.

     

    ## 다시 위의 코드를 본다면, 각 클래스에서 안녕하세요가 반복되는 것을 알 수 있는데

    ## 위와 같이 오버라이딩된 메서드에서 super()로 기반 클래스의 메서드를 호출해보자.

    class Person:
        def greeting(self):
        print('안녕하세요.')
    
    class Student(Person):
        def greeting(self):
        super().greeting() # 기반 클래스의 메서드 호출하여 중복을 줄임
        print('저는 파이썬 코딩 도장 학생입니다.')
    
    
    james = Student()
    
    james.greeting()
    
    #
    안녕하세요
    저는 파이썬 코딩 도장 학생입니다.

    ### 즉, 중복되는 기능은 파생 클래스에서 다시 만들지 않고, 기반 클래스의 기능을 사용하면 된다.

    ### 이처럼 메서드 오버라이딩은 원래 기능을 유지하면서 새로운 기능을 덧붙일 때 사용한다.


    ## 다중 상속 사용하기

     ## 다중 상속이란 아래와 같이 여러 기반 클래스로부터 상속받아서 파생 클래스를 만드는 방법이다.

    class 기반클래스이름1:
        코드
    
    class 기반클래스이름2:
        코드
    
    class 파생클래스이름(기반클래스이름1, 기반클래스이름2):
        코드 

    ## 다중 상속 예를 들어보자

    # 사람, 대학교, 대학생 여기서는 대학생이 사람과 대학교의 다중 상속된 파생클래스이다.

    class Person:
        def greeting(self):
        print('안녕하세요.')
    
    class University:
        def manage_credit(self):
        print('학점 관리')
    
    class Undergraduate(Person, University):
        def study(self):
        print('공부하기')
    
    
    james = Undergraduate()
    
    james.greeting() # 안녕하세요.: 기반 클래스 Person의 메서드 호출
    
    james.manage_credit() # 학점 관리: 기반 클래스 University의 메서드 호출
    
    james.study() # 공부하기: 파생 클래스 Undergraduate에 추가한 study 메서드
    #
    안녕하세요.
    학점 관리
    공부하기

     ## 먼저 기반 클래스 Person  기반 클래스 University  ,(콤마)를 구분으로 해서 작성한다.

    ## 이렇게 하면 두 기반 클래스의 기능을 모두 상속 받게 된다.

     

    ## 다이아몬드 상속 ##

    class A:
        def greeting(self):
        print('안녕하세요. A입니다.')
    
    class B(A):
        def greeting(self):
        print('안녕하세요. B입니다.')
    
    class C(A):
        def greeting(self):
        print('안녕하세요. C입니다.')
    
    class D(B, C):
        pass
    
    x = D()
    
    x.greeting()
    ::
    안녕하세요. B입니다.

    ## 여기서 D는 B와 C 두 기반 클래스의 파생클래스로 B,C 로 입력하여 우선순위가 B가 먼저이기에 B의 값이 출력된다.

    # 파이썬은 다중 상속을 한다면 class D(B, C):의 클래스 목록 중 왼쪽에서 오른쪽 순서로 메서드를 찾는다.

    # 그러므로 같은 메서드가 있다면 B가 우선합니다.

       만약 상속 관계가 복잡하게 얽혀 있다면 MRO를 살펴보는 것이 편리하다.

    ## 이는 메서드 탐색 순서를 확인하면 알 수 있다.

     

    ## 메서드 탐색 순서 확인하기

     ## 많은 프로그래밍 언어들이 다이아몬드 상속에 대한 해결책을 제시하고 있는데

         파이썬에서는 메서드 탐색 순서(Method Resolution Order, MRO)를 따른다.

    ## 다음과 같이 클래스 D에 메서드 mro를 사용해보면 메서드 탐색 순서가 나옵니다(클래스.__mro__ 형식도 같은 내용)

    # 클래스.mro()

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

    # 클래스 간의 관계가 다이아몬드 모양을 닯았다고 하여

       객체지향 프로그래밍에서는 이런 상속 관계를 다이아몬드 상속이라 부른다.

    ## 결론부터 말하자면 프로그래밍에서는 이렇게 명확하지 않고 애매한 상태를 피해야야만 한다. ##

     

    ## object 클래스

    ## 파이썬에서는 object 클래스는 모든 클래스의 조상이다.

    # int의 MRO를 출력해보면 int 자기 자신과 object가 출력됩니다.

    print(int.mro())
    ::
    [<class 'int'>, <class 'object'>]



    # 다이 아몬드 상속에 대한 의견_코딩도장

    여기서는 클래스 A를 상속받아서 B, C를 만들고, 클래스 B와 C를 상속받아서 D를 만들었습니다.

    그리고 A, B, C 모두 greeting이라는 같은 메서드를 가지고 있다면 D는 어떤 클래스의 메서드를 호출해야 할까요? 조금 애매합니다.

    프로그래밍에서는 이렇게 명확하지 않고 애매한 상태를 좋아하지 않습니다.

    프로그램이 어떨 때는 A의 메서드를 호출하고, 또 어떨 때는 B 또는 C의 메서드를 호출한다면 큰 문제가 생깁니다.

    만약 이런 프로그램이 우주선 발사에 쓰인다면 정말 끔찍합니다.

    그래서 다이아몬드 상속은 문제가 많다고 해서 "죽음의 다이아몬드"라고도 부릅니다.


    ## 추상 클래스 사용하기

     ## 추상 클래스는 메서드의 목록"만" 가진 클래스이며 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용한다.

    # 파이썬에서는 추상 클래스라는 기능을 제공하는데 abc 모듈을 불러옴과 동시에 아래의 형식으로 만들 수 있다.

    # ( abc는 abstract base class의 약자이다)

    # 만약 import abc로 모듈을 가져왔다면 abc.ABCMeta, @abc.abstractmethod로 사용해야 한다.

    from abc import *
    
    class 추상클래스이름(metaclass=ABCMeta):
        @abstractmethod
        def 메서드이름(self):
            코드

     

    ## 추상클래스를 만들고 이를 상속하는 학생 클래스르 만들어보자.

    from abc import *
    
    class StudentBase(metaclass=ABCMeta):
        @abstractmethod
        def study(self):
            pass
    
        @abstractmethod
        def go_to_school(self):
            pass
    
    class Student(StudentBase):
        def study(self):
        print('공부하기')
    
    
    james = Student() ## error
    james.study()
    #
    Traceback (most recent call last):
    File "/Users/munseunghui/playground/python_Study/36.6추상클래스 사용하기.py", line 31, in <module>
    james = Student()
    TypeError: Can't instantiate abstract class Student with abstract methods go_to_school
    
     

    ## 이를 실행시켜보면 Student 메서드에 james 라는 인스턴스를 만드는 순간부터 error가 남을 알 수 있다.

    ## 에러의 이유는 아래와 같다.

    ## 먼저 StudentBase라는 추상클래스에서는 study go_to_school 두 가지 메서드를 정의하였다.

    ## 하지만 추상클래스를 상속받은 Student 클래스에서는 study만을 정의하고 있어 에러가 난것이다.

    ## 따라서 추상 클래스를 상속받았다면 @abstractmethod가 붙은 추상 메서드를 "모두" 구현해야 한다.

     

     

    ## 다음과 같이 Student에서 go_to_school 메서드도 구현해줍니다.

    from abc import *
    
    class StudentBase(metaclass=ABCMeta):
        @abstractmethod
        def study(self):
            pass
    
        @abstractmethod
        def go_to_school(self):
            pass
    
    class Student(StudentBase):
        def study(self):
        print('공부하기')
    
        def go_to_school(self):
        print('학교가기')
    
    james = Student() ## 추상클래스의 추상 메서드를 모두 구현했는지 확인되는 시점
    
    james.study() # 공부하기
    
    james.go_to_school() # 학교가기

    ### 이처럼 추상클래스는 파생클래스가 반드시 구현해야하는 메서드를 정해줄 수 있다.

    ## 참고로 추상 클래스의 추상 메서드를 모두 구현했는지 확인하는 시점은 파생 클래스가 인스턴스를 만들 때이다.

    # 따라서 james = Student()에서 확인합니다(구현하지 않았다면 TypeError 발생).

     

     

    ### 추상 클래스를 빈 메서드로 만들어야만 하는 이유

    ## 추상 클래스는 인스턴스로 만들 수 없다 --> 빈 메서드로 만들어야하는 가장 큰 이유이다 ##

    ## 따라서 추상 클래스의 추상 메서드를 만들때 pass만 넣어서 빈 메서드로 만든것이다.

    ## 왜냐하면 추상 클래스는 인스턴스를 만들 수 없으니, 추상 메서드로 호출 할 일 이 없기 때문이다.##

    @abstractmethod
    def study(self):
        pass # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦
    
    @abstractmethod
    def go_to_school(self):
        pass # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦

    ## 아래와 같이 추상클래스로 인스턴스를 생성하면 error가 발생한다.

    james = StudentBase()
    
    Traceback (most recent call last):
    File "<pyshell#3>", line 1, in <module>
    james = StudentBase()
    TypeError: Can't instantiate abstract class StudentBase with abstract methods go_to_school, study

     ## 즉, 추상 클래스는 인스턴스로 만들 때 사용하지 않으며 오로지 상속에만 사용한다.

    ## 그리고 파생 클래스에서 반드시 구현해야 하는 메서드를 정해 줄 때 사용한다.


    # 지금까지 상속에 대해 알아보았는데 내용이 다소 어려웠습니다.

    ## 여기서는 클래스를 상속받는 방법과 메서드 오버라이딩 방법 정도만 기억하면 됩니다.

    ## 그리고 상속은 같은 종류이면서 동등한 기능일 때 사용한다는 점 중요합니다.

    # 다중 상속과 추상 클래스는 나중에 필요할 때 다시 돌아와서 찾아보자.

     

    ## 오버라이딩을 사용했다고 해서 기반 클래스의 메서드를 호출 할 수 없는 것은 아니다.

     

    728x90
Designed by Tistory.