-
[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()를 커스텀할 수 있습니다.