Media Log

1. 상속(Inheritance)

 

오늘은 클래스의 상속(Inheritance)에 대해 알아보려고 합니다. 상속이란 무엇일까요? 우리가 알고 있는 상속은 사전적 정의에 따르면 "일정한 친족적 관계가 있는 사람 사이에 한 쪽이 사망하거나 법률상의 원인이 발생하였을 때 재산적 또는 친족적 권리와 의무를 계승하는 제도"와 같습니다. 그런데 이 상속이란 개념이 파이썬의 클래스에도 존재합니다. 쉽게 말해서, 부모의 유산을 자식이 물려 받듯이 부모 클래스와 자식 클래스라는 것이 존재하여 부모 클래스의 멤버를 자식 클래스가 물려받을 수 있습니다.

위 그림처럼 상속 관계에 있다고 가정해봅시다. 먼저 사람(Person) 클래스는 부모가 되는 클래스이니 부모 클래스라고 말하고, 근로자(Employee) 클래스는 자식이 되는 클래스이니 자식 클래스라 말합니다. Employee 클래스는 어찌보면 Person의 영역 안에 속하는 것이므로 상속을 통해 Person 클래스의 멤버(함수, 데이터)를 물려받아 Employee 클래스에서 그대로 사용할 수 있습니다. 아직까지 상속이란 녀석이 어떤 녀석인지 모르시겠다면 아래의 예제를 우선 보도록 합시다.

>>> class Person:
	def __init__(self, name, age, gender):
		self.Name = name
		self.Age = age
		self.Gender = gender
	def aboutMe(self):
		print("저의 이름은 " + self.Name + "이구요, 제 나이는 " + self.Age + "살 입니다.")
		
>>> class Employee(Person):
	def __init__(self, name, age, gender, salary, hiredate):
		Person.__init__(self, name, age, gender)
		self.Salary = salary
		self.Hiredate = hiredate
	def doWork(self):
		print("열심히 일을 합니다.")
	def aboutMe(self):
		Person.aboutMe(self)
		print("제 급여는 " + self.Salary + "원 이구요, 제 입사일은 " + self.Hiredate + " 입니다.")

>>> objectEmployee = Employee("김철수", "18", "남", "5000000", "2013년 10월 28일")
>>> objectEmployee.doWork()
열심히 일을 합니다.
>>> objectEmployee.aboutMe()
저의 이름은 김철수이구요, 제 나이는 18살 입니다.
제 급여는 5000000원 이구요, 제 입사일은 2013년 10월 28일 입니다.

위 예제를 보시면, Person이란 클래스와 Person 클래스를 상속받는 Employee 클래스가 정의되어 있습니다. 아직은 모르는 것 투성이지만, 차근차근 예제의 코드를 살펴보도록 합시다. 우선은 Person 클래스의 내부부터 보도록 합시다. Person 클래스의 생성자에서는 이름, 나이, 성별을 인자로 넘겨받고 self.Name, self.Age, self.Gender의 값을 초기화 시키는 것을 보실 수 있습니다. 또한 aboutMe라는 함수는 이름과 나이를 출력하는 함수입니다. 그 다음으로 Employee 클래스를 보도록 합시다.

>>> class Employee(Person):

이 부분을 자세히 보시면, 클래스 이름 뒤에 괄호가 등장하여 괄호 안에 클래스 이름이 또다시 등장하는 것을 보실 수 있습니다. 이것은 Employee 클래스가 Person 클래스를 상속받는다는 의미이며, 이는 아래와 같이 표현이 된다는 것을 알 수 있습니다.

class 클래스명(상속받을 클래스명):

그리고 Employee 클래스가 Person 클래스를 상속받으니, Employee 클래스는 멤버를 물려받는 자식 클래스라 말할 수 있으며 Person 클래스는 멤버를 물려주는 부모 클래스라고 말할 수 있습니다. 이어서 Employee 클래스의 생성자 부분을 보도록 합시다. 생성자 부분을 보시면 특이한 부분을 보실 수 있는데, 한번 같이 보도록 합시다.

...
	def __init__(self, name, age, gender, salary, hiredate):
		Person.__init__(self, name, age, gender)
		self.Salary = salary
		self.Hiredate = hiredate
...

위의 생성자에선 이름, 나이, 성별, 급여, 입사일을 인자로 넘겨받는다는 것을 보실 수 있습니다. 그런데 특이한건, 위 코드의 3행을 보시면 부모 클래스의 생성자를 호출하면서 넘겨받은 인자를 부모 클래스의 생성자로 넘겨주는데, 이는 명시적으로 Person 클래스의 생성자를 호출하는 방법입니다. (여기서는 'self'를 함께 전달해야 합니다) 이어서, Employee 클래스의 aboutMe 함수를 보도록 하겠습니다.

...
	def aboutMe(self):
		Person.aboutMe(self)
		print("제 급여는 " + self.Salary + "원 이구요, 제 입사일은 " + self.Hiredate + " 입니다.")
...

위의 aboutMe 함수를 살펴보시면 이 함수에서도 명시적으로 Person 클래스의 aboutMe 함수를 호출하는 것을 보실 수 있습니다. 부모 클래스의 aboutMe 함수가 호출되고 나서는 급여와 입사일을 출력합니다. 이렇게 보니, 참으로 상속이란 특징이 쓸모있다고 여겨지지 않나요? 이 상속이란 특징을 이용하면, 유지 보수가 쉬워지거나 중복되는 코드가 적어지는 등의 장점이 존재합니다. 위의 예제처럼 한개의 클래스를 상속받는 경우도 있지만, 두개 이상의 클래스를 상속 받는 경우도 있을 수 있습니다. 한번 보실까요?

 

2. 다중 상속(Multiple Inheritance)

 

다중 상속이란 두개 이상의 클래스를 상속받는 것을 말하는데, 이 경우에는 두 클래스의 모든 속성을 물려받게 됩니다. 이는 하나의 자식 클래스가 두개 이상의 부모 클래스를 가지는 것이라고 할 수 있습니다. 아래 그림처럼 말입니다.

다중 상속의 예제를 한번 보도록 합시다. 그리고 차근차근 예제의 코드를 살펴보도록 합시다.

>>> class ParentOne:
	def func(self):
		print("ParentOne의 함수 호출!")
	
>>> class ParentTwo:
	def func(self):
		print("ParentTwo의 함수 호출!")
	
>>> class Child(ParentOne, ParentTwo):
	def childFunc(self):
		ParentOne.func(self)
		ParentTwo.func(self)
		
>>> objectChild = Child()
>>> objectChild.childFunc()
ParentOne의 함수 호출!
ParentTwo의 함수 호출!
>>> objectChild.func()
ParentOne의 함수 호출!

위의 예제 코드에서는 부모 클래스인 ParentOne, ParentTwo라는 클래스가 정의되어 있으며, 자식 클래스인 Child라는 클래스도 정의되어 있습니다. 위의 코드에서 9행을 한번 보실까요?

>>> class Child(ParentOne, ParentTwo):

위 코드에서는 2개 이상의 클래스를 상속 받을때 콤마를 기준으로 상속받을 클래스의 이름을 나열하고 있는 것을 보실 수 있습니다. (여기서 상속받을 클래스의 나열 순서가 검색 결과에 영향을 끼칩니다) 그리고 주의깊게 보셔야 할 부분은, 18~19행의 부분인데 한번 보도록 합시다. 

>>> objectChild.func()
ParentOne의 함수 호출!

위 부분을 보시면 objectChild의 func 함수를 호출하는데, 여기서 ParentOne과 ParentTwo 중 어떤 클래스의 func 함수가 호출되는지 보았더니 ParentOne 클래스의 func 함수가 호출된 모습을 보실 수 있습니다. 이는, 우리가 상속받을 클래스의 이름을 나열할때 순서에 따라 이름을 찾기 때문입니다. 즉, ParentOne 클래스의 이름공간에서 func를 찾는다는 것입니다. 다중 상속에 대해 이해가 가시나요? 그런데 다중 상속을 이용할때도 주의하셔야 할 점이 있는데, 아래와 같은 다이아몬드 상속을 하는 경우 문제가 발생합니다.

위 그림을 보시면 B와 C 클래스가 A 클래스를 상속받고 있으며, 다시 D 클래스가 B와 C 클래스를 상속받고 있습니다. 위 처럼 다이아몬드 모양의 상속 구조에서는 아래와 같은 문제가 발생할 수 있습니다.

>>> class A:
	def __init__(self):
		print("A 클래스의 생성자 호출!")
		
>>> class B(A):
	def __init__(self):
		A.__init__(self)
		print("B 클래스의 생성자 호출!")
	
>>> class C(A):
	def __init__(self):
		A.__init__(self)
		print("C 클래스의 생성자 호출!")
	
>>> class D(B, C):
	def __init__(self):
		B.__init__(self)
		C.__init__(self)
		print("D 클래스의 생성자 호출!")
	
>>> objectD = D()
A 클래스의 생성자 호출!
B 클래스의 생성자 호출!
A 클래스의 생성자 호출!
C 클래스의 생성자 호출!
D 클래스의 생성자 호출!

위 예제를 보시면, B와 C에선 부모 클래스인 A의 생성자를 호출하고 있으며 D에서는 부모 클래스인 B와 C 클래스의 생성자를 호출하고 있는 모습을 보실 수 있습니다. 이 상태에서 D의 인스턴스 객체를 생성하게 되면, A 클래스의 생성자가 두번이나 호출되고 있는 모습을 보실 수 있습니다. 이 상황을 피하기 위해서 super라는 내장 함수를 사용할 수 있으며, 이 함수는 부모 클래스의 객체를 반환하게끔 되어 있습니다. 한번 내장 함수인 super 함수를 이용하여 위의 예제를 고쳐보도록 하겠습니다.

>>> class A:
	def __init__(self):
		print("A 클래스의 생성자 호출!")

>>> class B(A):
	def __init__(self):
		print("B 클래스의 생성자 호출!")
		super().__init__()

>>> class C(A):
	def __init__(self):
		print("C 클래스의 생성자 호출!")
		super().__init__()

>>> class D(B, C):
	def __init__(self):
		print("D 클래스의 생성자 호출!")
		super().__init__()
	
>>> objectD = D()
D 클래스의 생성자 호출!
B 클래스의 생성자 호출!
C 클래스의 생성자 호출!
A 클래스의 생성자 호출!

위에서 super 함수를 통하여 생성자를 호출하도록 하니, 정상적으로 모든 클래스의 생성자가 한번씩 호출되는 결과를 얻을 수 있습니다. 여기서 B와 C 클래스가 A 클래스의 자식인 것을 생각하여 인터프리터가 A 클래스의 생성자가 두번이나 호출되는 것을 피하는 방법입니다. 괜찮죠? 다중 상속이 필요하여 사용할 경우에는 다중 상속에 대해 충분한 이해를 하신 뒤에 다중 상속을 사용하시는 것을 권해드립니다.

저작자 표시 비영리 변경 금지
신고
  1. ZiNee at 2013.11.01 14:50 신고 [edit/del]

    다중상속은 참으로 유용할 것 같은 예감이 듭니다. 좋은 강좌 감사합니다.

    Reply
  2. 초짜 at 2013.11.19 20:52 신고 [edit/del]

    정말정말정말 잘 보고 있습니다!!!!

    Reply
  3. 민우 at 2014.01.21 23:04 신고 [edit/del]

    강좌 잘보고있습니다 감사합니다.^^
    그런데 파이썬2.7에서는 에러가 나던데 super키워드가 적용이 안되나요???
    적용이 안된다면 다른 해결책이 있을까요?ㅠ

    Reply
    • Mdst at 2014.03.07 18:30 신고 [edit/del]

      파이썬 2.7에서는 super 문법이 조금 다릅니다.
      이렇게 해보세요
      class C(B):
      def __init__(self):
      super(C, self).__init__()

    • 데자와 at 2015.02.02 17:09 신고 [edit/del]

      2.7에서는
      class A(object): 로 해줘야 하며
      class B(A, object): ~~ super(B, self).__init__()
      그리고 두개의 부모 클래스를 가지는 D같은 경우
      class D(B,C, object): ~~ 이렇게 써줘야 되네요.
      에러가 나는 이유가 super에 쓰이는 매개변수의 타입이 type이어야 하는데 그냥 B(A)로 정의해줄 경우 타입이 classobj으로 정해지기 때문이라네요.

  4. 쏭지 at 2014.04.22 06:44 신고 [edit/del]

    완전 감사합니다...이번학기 파이썬으로 공부하는데...완전 좋네요...감사

    Reply
  5. 이제시작 at 2014.06.02 15:34 신고 [edit/del]

    제일 마지막 다중상속 예제에서 왜 출력되는게 A, B, C, D 순으로 나오지 않는거죠?
    class D에 인자가 B가 먼저 들어갔으니.. class B(A) 가 먼저 호출되어서 A, B 가 먼저 나와야 할것 같은데- A, C가 나오네요... 이해가 ㅠㅠ

    Reply
    • BlogIcon EXYNOA at 2014.06.03 16:40 신고 [edit/del]

      탐색 순서는 아시고 계시듯 D -> B -> C -> A 순으로 되어 A, B, C, D가 출력되는 것이 맞습니다. 그러나 내장 출력함수가 생성자 아래에서 사용되어, 'D 생성자 호출 -> B 생성자 호출 -> C 생성자 호출 -> A 생성자 호출 -> A 생성자 빠져나옴 -> C 생성자 빠져나옴 -> B 생성자 빠져나옴 -> D 생성자 빠져나옴' 이런식의 순서를 거치면서 A, C, B, D와 같이 출력된 것입니다. MRO 함수 혹은 멤버를 통해 클래스의 탐색 순서를 다음과 같이 알 수 있습니다.

      >>> D.__mro__
      (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

      D 생성자 -> B 생성자 -> C 생성자 -> A 생성자 순으로 호출이 된다는 것입니다. 우선 오해의 소지가 없도록 예제를 변경해 두었습니다.

    • 궁금합니다 at 2017.08.19 16:36 신고 [edit/del]

      변경하니, 더 오해가 생겼습니다;;

  6. 뭉치 at 2014.06.03 12:07 신고 [edit/del]

    저도 마지막 부분 출력이 왜 저렇게 되는지 설명 좀 부탁드립니다 ㅠ.ㅠ

    Reply
  7. 가루니스 at 2014.06.17 20:57 신고 [edit/del]

    흐음....저도 마지막 부분이 잘 이해가 가지않네요 본문 내용을 보면 class D의 생성자는 print("D 생성자호출!")을 먼저하고 super().__init__()을 해줬는데 print문이 먼저 실행되야하는거 아닌가요???왜 super문이 먼저실행되는지 잘 모르겠네요...설명 부탁드리겠습니다.

    Reply
    • BlogIcon EXYNOA at 2014.06.17 23:37 신고 [edit/del]

      죄송합니다.. 뭉치님과 이제시작님이 내용을 지적해주셔서 본문 내용을 수정하는데 예제만 수정해버리고 결과값은 그대로 둬버렸네요. 가루니스님이 알고 계시듯 먼저 print -> 생성자 -> ... 순으로 출력되는 것이 맞습니다! 본문 내용은 다시 수정했습니다.

    • 가루니스 at 2014.06.19 09:33 신고 [edit/del]

      네 감사합니다....저도 한참 생각하다가 뭐가 답이 안맞길래...늘 좋은 강의 감사합니다 많이 배워갑니다!!

  8. 궁금합니다 at 2017.08.19 16:34 신고 [edit/del]

    super 예시에서 정확한 코드 비교를 위해서라면 첫번째 super 예시 코드에서 나온 print의 순서를 유지하면서 두번째 super 예시 코드로 실행하는 게 좋아보이는데, 순서를 바꾸신 이유가 있으신가요?

    Reply

submit

티스토리 툴바