파이썬 클래스 상속 이해하기

파이썬 클래스 상속 이해하기

상속(inheritance)이란 하나의 클래스가 다른 클래스의 속성과 메서드를 얻는 과정을 의미한다. 새롭게 형성된 클래스는 자녀 클래스(child class)라고 부르고, 자녀 클래스가 파생된 클래스를 부모 클래스(parent class)라고 부른다.

자녀 클래스는 부모 클래스의 속성과 메서드를 오버라이딩하고 확장할 수 있다. 다른 말로, 자녀 클래스는 부모 클래스로 부터 모든 속성과 메서드를 상속 받지만, 또한 자녀 클래스 안에서 고유한 속성과 메서드를 명시해줄 수 있다는 것이다.


부모 클래스 vs. 자녀 클래스

각 개 품종의 자녀 클래스를 생성하기 위해, 먼저 Dog 클래스의 정의를 참고해보자.

class Dog:
	species = "Canis familiaris"

	def __init__(self, name, age):
		self.name = name
		self.age = age

	def __str__(self):
		return f"{self.name} is {self.age} years old"
	
    def speak(self, sound):
		return f"{self.name} says {sound}"

자녀 클래스를 생성하기 위해서는 별도의 이름을 가진 새로운 클래스를 생성하고 괄호 안의 부모 클래스의 이름을 넣어준다. 다음의 예제는 Dog 클래스의 새로운 자녀 클래스를 생성한다.

class JackRussellTerrier(Dog):
	pass

class Dachshund(Dog):
	pass

class Bulldog(Dog):
	pass

자녀 클래스가 정의 되었으므로, 특정 품종의 개들을 인스턴스화 할 수 있다.

>>> miles = JackRussellTerrier("Miles", 4)
>>> buddy = Dachshund("Buddy", 9)
>>> jack = Bulldog("Jack", 3)
>>> jim = Bulldog("Jim", 5)

자녀 클래스의 인스턴스는 부모 클래스로 부터 모든 속성과 메서드를 상속 받는다.

>>> miles.species
'Canis familiaris'

>>> buddy.name
'Buddy'

>>> print(jack)
Jack is 3 years old

>>> jim.speak("Woof")
'Jim says Woof'

객체가 어떤 클래스에 속해 있는지 확인 하려면 type() 내장 함수를 사용하면 된다.

>>> type(miles)
<class '__main__.JackRussellTerrier'>

만약 객체 milesDog 클래스의 인스턴스인지 확인하고 싶다면 isinstance() 내장 함수를 사용하면 된다.

>>> isinstance(miles, Dog)
True

isinstance()는 객체와 클래스, 두 가지의 인수를 취한다. 위 예제에서는 isinstance() 함수를 사용해서 milesDog 클래스의 인스턴스 인지 체크했고 True가 반환되었다.

miles, buddy, jim 모두 Dog 클래스의 인스턴스 이지만, miles는 Bulldong의 인스턴스가 아니고, 마찬가지로 jack도 Dashhund의 인스턴스가 아니다.

>>> isinstance(miles, Bulldog)
False

>>> isinstance(jack, Dachshund)
False
정리하자면, 자녀 클래스에서 생성된 모든 객체들은 부모 클래스의 인스턴스이지만, 다른 자녀 클래스의 인스턴스는 아니다.

부모 클래스의 기능 확장하기

지금까지 Dog 부모 클래스와 세 개의 자녀클래스, 총 네 개의 클래스가 생성되었다. 모든 자녀 클래스는 부모 클래스로부터 모든 속성과 메소드를 상속받는데, 부모 클래스인 Dog 클래스의 speak() 메서드 또한 포함된다.

개 품종마다 각각 다르게 짖기 때문에, 각각의 speack() 메서드의 sound 인수에 대한 기본값을 제공한다고 가정해보자. 그러기 위해서는 각 품종의 클래스 정의 안에서 speak() 메서드를 오버라이딩 해야한다. 부모 클래스로 부터 정의된 메서드를 오버라이딩하려면, 먼저 자녀 클래스 안에서 같은 메서드 이름을 정의해야 한다.

class JackRussellTerrier(Dog):
	def speak(self, sound="Arf"):
		return f"{self.name} says {sound}"

위 예제에서는 speak() 메서드가 JackRussellTerrier 클래스 안에서 기본 인수 sound="Arf"와 함께 정의 되었다. 이렇게 함으로써, 더 이상 speak() 메서드와 JackRussellTerrier 인스턴스에 인수를 전달하지 않고도 호출할 수 있게 된다.

>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles says Arf'

만약 개가 다른 소리로 짖을 때, 여전히 speak() 메서드에 다른 인수를 전달할 수 있다.

>>> miles.speak("Grrr")
'Miles says Grrr'

클래스 상속의 한 가지 장점은 부모 클래스의 변경 사항이 자녀 클래스로 바로 적용된다는 점이다. 이 부분은 자녀 클래스에서 속성이나 메서드가 오버라이딩 되지 않은 경우에만 적용된다.

class Dog:
	# Other attributes and methods omitted...

	def speak(self, sound):
		return f"{self.name} barks: {sound}"
        
>>> jim = Bulldog("Jim", 5)
>>> jim.speak("Woof")
'Jim barks: Woof'

하지만 JackRussellTerrier 인스턴스의 speak() 메서드는 오버라이딩 되었음으로 부모 클래스의 변경 사항이 적용 안된 것을 확인할 수 있다.

>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles says Arf'

부모 클래스의 메서드를 전적으로 오버라이딩해서 사용할 수도 있지만, 지금 같은 경우는 부모 클래스 메서드의 변화된 출력을 사용하고 싶다고 가정해보자. 그러기 위해서는, 여전히 speak() 메서드를 JackRussellTerrier 클래스에 정의해주어야 한다. 하지만 출력 문자열을 다시 재정의하는 대신, 자녀 클래스의 speak() 메서드 안에서 부모 클래스 Dogspeak() 메서드를 호출해야 한다. 이는  JackRussellTerrier.speak()에 어떤 인수가 sound 인수에 전달되어도 부모 클래스 Dog.speak()로 전달하기 위함이다.

자녀 클래스의 메서드 안에서 super() 함수를 사용하면 부모 클래스에 접근할 수 있다.

class JackRussellTerrier(Dog):
	def speak(self, sound="Arf"):
		return super().speak(sound)

JackRussellTerrier 클래스 안에서 super().speak(sound)를 호출하면, 파이썬은 부모 클래스인 Dogspeak() 메서드를 찾고 변수 sound메서드를 호출한다. 다음 예제에서 miles.speak()를 호출하면, Dog 클래스에서 새롭게 반영된 포맷 형식의 출력을 확인할 수 있다.

>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles barks: Arf'
>>> miles.speak("Grrr")
'Miles barks: Grrr'

Reference