ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 상속
    언어/파이썬 & 장고 2016. 10. 7. 15:05

    상속

    class Base:
        def base_method(self):
            print("base_method")
    class Derived(Base):
        pass
    base = Base()
    base.base_method()
    
    derived = Derived()
    derived.base_method() # Derived 클래스가 스스로 구현한 메소드는 없지만 Base로부터무려받은 base_method()는 갖고 있음
    


    코드를 재사용하기 위해선 상속보단 클래스의 인스턴스를 데이터 속성으로 갖는 것이 상속보다 더 나은 코드 재사용 방법이 될 수 있습니다. 이런 기법을 포함(containment)라고 합니다. 다음은 B클래스가 A클래스를 상속하는 대신 __init__() 메소드 안에서 A의 인스턴스를 자신의 데이터 속성으로 정의하고 call_methodA() 메소드는 이 A의 인스턴스를 이용하여 method()를 호출하는 예입니다.

    class A:
        def methodA():
            pass
    class B:
        def __init__(self):
            self.instance_of_A = A()
        def call_methodA(self):
            self.instance_of_A.methodA()

    상속은 자식클래스가 부모 클래스의 모습을 그대로 이어야 할 때 적합합니다. 가령 다음과 같이 컴퓨터의 로컬 디스크 파일에 로그를 남기는 Logger 클래스가 있는데 여기에 네트워크로 로그를 전송하는 기능을 추가하고 싶다면 포함보다는 상속을 이용하는 것이 좋습니다.

    class Logger:
        def writeOnFile(self):
            pass
    class NetworkLogger(Logger): # Logger로부터 writeOnFile()을 물려받고 writeOnNetwork를 추가로 구현합니다.
        def writeOnNetwork(self):
            pass



    OOP에서 상속의 중요한 특징 중 하나는 "자식클래스의 객체를 부모 클래스의 객체로 간주한다" 입니다. 자식 클래스는 부모의 기능을 그대로 물려받기 때문에 부모 행세를 할 수 있습니다.

    # -*- coding: utf-8 -*-
    class Armor:
        def armor(self):
            print("armor")
    
    def get_armored(suite):  # get_armored() 함수는 Armor클래스를 염두에 두고 작성된 함수입니다. 매개변수로 입력받은 Armor 객체의 armor() 메소드를 호출합니다
        suite.armor()
    suite = Armor()
    get_armored(suite)  # get_armored()함수를 호출하면 Armor 클래스의 armor()메소드가 호출됩니다.
    # 결과
    #armor
    class Man(Armor):
        pass
    man = Man() 
    get_armored(man) # Armor 클래스가 사용되던 get_armored() 함수를 바꾸지 않고 그대로 Man 클래스의 객체를 매개변수로 입력
    # 결과
    #armor


    한편 상속을 받았다면 당연히 데이터 속성도 물려받아야 합니다. 다음 A의 데이터 속성인 message가 B에게 제대로 상속되었는지 확인하는 예를 들겠습니다.

    # -*- coding: utf-8 -*-
    class A:
        def __init__(self):
            print("A.__init__()")
            self.message="HELLO"
    class B(A):
        def __init__(self):
            print("B.__init__()")
    obj = B()
    # 결과
    # B.__init__()
    obj.message
    # 결과
    # AttributeError: B instance has no attribute 'message'

    에러의 이유는 message는 A의 __init__() 메소드 안에서 생성되는데, B의 인스턴스를 생성하면서 B.__init__()만 호출하고 A.__init__() 메소드는 호출하지 않았기 때문입니다.

    아래와 같이 코드를 고치면 에러를 고칠 수 있습니다.

    # -*- coding: utf-8 -*-
    class A:
        def __init__(self):
            print("A.__init__()")
            self.message="HELLO"
    class B(A):
        def __init__(self):
            A.__init__(self)
            print("B.__init__()")
    obj = B()
    # 결과
    # A.__init__()
    # B.__init__()
    print(obj.message)
    # 결과
    # HELLO

    위 코드의 한계점은 만약 B의 기반 클래스를 A가 아닌 Z 클래스로 변경해야 하는 일이 생긴다면 class B(A)를 class B(Z)로 바꾸는 것뿐만 아니라 B클래스 안에서 A객체에 접근하는 모든 코드를 수정해야합니다. 이는 super()를 통해 해결할 수 있습니다.

    상속할 부모 클래스를 명시하지 않으면 기본값으로 object를 상속받습니다.

    super()

    super()는 부모 클래스의 객체 역할을 하는 프록시를 반환하는 내장함수입니다. super() 함수의 반환값을 상위클래스의 객체로 간주하고 코딩해도 상관 없습니다.

    # -*- coding: utf-8 -*-
    class A:
        def __init__(self):
            self.message="HELLO"
    class B(A):
        def __init__(self):
            super().__init__() # A.__init__(self)와 동일한 결과

    super()를 사용ㅎ면 부모 클래스가 다른 클래스로 교체되거나 수정되어도 자식 클래스들이 받는 영향을 최소화 할 수 있습니다.


    # -*- coding: utf-8 -*-
    class A:
        def __init__(self):
            print("A.__init__()")
            self.message = "HELLO"
    
    class B(A):
        def __init__(self):
            print("B.__init__()")
            super().__init__()
            print("self.message : " + self.message)
    
    obj = B()
    # 결과
    # B.__init__()
    # A.__init__()
    # self.message : HELLO

    여기서 주의할 점은 super()로 사용하는 것은 python 3.x에서만 가능하고 2.x는 에러가 발생합니다.

    super()없이 상위 클래스의 __init__() 메소드를 호출하는 경우

    파생클래스의 구현이 다음과 같이 비워져 있는 경우, 파생클래스의 인스턴스를 생성할 때 부모 클래스의 __init__() 메소드가 호출됩니다.

    # -*- coding: utf-8 -*-
    class A:
        def __init__(self):
            print("A.__init__()")
    class B(A):
        pass
    obj = B() # A.__init__() 메소드가 호출
    # 결과
    # A.__init__()
    
    


    다중 상속

    -----------------------

    오버라이딩

    오버라이딩은 부모클래스로부터 상속받은 메소드를 다시 정의하는 것입니다.

    # -*- coding: utf-8 -*-
    class A:
        def method(self):
            print("A")
    class B(A):
        def method(self):
            print("B")
    class C(A):
        def method(self):
            print("C")
    A().method()
    # A
    B().method()
    # B
    C().method()
    # C

    위 처럼 재정의를 할 수도 있고 그대로 부모 클래스의 메소드를 사용할 수도 있습니다. 추가적으로 재정의를 하면서도 부모 버전의 메소드를 활용할 수 있는 방법이 있는데 super() 함수를 사용하면 됩니다.

    # -*- coding: utf-8 -*-
    
    class Car:
        def ride(self):
            print("RUN")
    
    class FlyingCar(Car):
        def ride(self):
            super().ride()
            print("fly")
    my_car = FlyingCar()
    my_car.ride()
    # 결과
    # RUN
    # fly
    

    super()함수를 이용해서 부모버전의 ride()를 호출하여 원래 기능을 유지하면서 ride()를 커스텀할 수 있습니다.

    댓글