[Python] 클래스 상속


Study/Python  2019. 12. 20. 09:00

안녕하세요. 명월입니다.


이 글은 Python에서 클래스 상속에 관한 글입니다.


이 전에 클래스에 관해 설명한 적이 있습니다.

링크 - [Python] 클래스 (Class)


Python에서 클래스를 생성하고 사용하는데, 똑같은 기능의 클래스에 기능을 좀 더 추가하고 싶을 경우가 있습니다. 그러나 이전에 사용한 클래스는 다른 여러 곳에서 참조 사용하기 때문에 수정하기 곤란한 경우가 있습니다.

그럴 때 클래스 코드를 복사 붙여넣기로 만들면 가능은 합니다만, 왠지 개발자답지 않는 스타일입니다.


그럴 때는 클래스의 온전한 기능을 상속을 받아 재정의 할 수 있습니다.

class Main():
  def __init__(self):
    # 언더바(_) 두개는 private이지만 한개는 protected의 의미이다.
    self._data = "Main Class";
  def exec_function(self):
    print(self._data);

# Main2함수는 Main의 함수를 상속받았다.
class Main2(Main):
  def __init__(self):
    self._data = "Main2 Class";

# 여기는 당연히 Main을 생성해서 exec_function를 호출했다.
a = Main();
a.exec_function();

# Main2에는 exec_function함수가 없지만 Main함수를 상속 받았기 때문에 Main함수의 exec_function를 호출한다.
b = Main2();
b.exec_function();

위 예제에서 Main2의 클래스는 분명히 exec_function를 선언하지 않았습니다. 그러나 Main 클래스를 상속받았기 때문에 Main의 기능을 온전히 사용할 수 있습니다.


여기서 data 변수 앞에 언더바(_)를 한 줄을 붙였습니다.

이 뜻은 protected의 의미입니다. 이 전에 언더바(_) 두 줄의 의미로 private라는 것을 설명했습니다.

링크 - [Python] 클래스 프로퍼티 (Property)


여기서 public과 private와 protected의 설명을 하면 public은 클래스 내부, 외부 모두 사용하는 객체입니다.언더바(_)가 없습니다.

언더바(_)가 하나의 경우는 protected의 의미로써, 클래스의 외부에서 사용할 수 없지만, 클래스 내부, 그리고 상속 받은 클래스에서 사용할 수 있습니다.

언더바(_)가 두개읜 경우는 private입니다. private는 해당 클래스 내부에서만 사용할 수 있고 외부와 상속 받은 클래스 모두에서 사용할 수 없습니다.


Main클래스에서 _data를 protected로 선언하였기 때문에, Main2에서도 _data의 값을 넣을 수 있습니다. exec_function에서는 Main2에서 설정한 _data의 값을 가져와서 출력하는 결과입니다.


Python의 상속에서는 부모가 생성한 함수를 재정의(override)할 수 있습니다.

class Main():
  def __init__(self):
    pass;
  def _get_data(self):
    return "Main Class";
  # exec_function함수에서는 protected get_data함수를 호출한다. get_data함수에서는 string 데이터를 리턴하고 그것을 출력하는 형태이다.
  def exec_function(self):
    print(self._get_data());
    
class Main2(Main):
  # Main 클래스에서 get_data함수를 만들었지만 Main2 클래스에서 재정의했다. 그러므로 exec_function에서는 Main2의 get_data함수를 호출하게 된다.
  def _get_data(self):
    return "Main2 Class";
    
a = Main();
a.exec_function();

b = Main2();
b.exec_function();

위 예제에서는 Main클래스에서 get_data함수를 만들었지만 Main2 클래스에서 재정의한 모습입니다. 결과는 Main2의 exec_function함수에서는 Main2의 get_data함수가 호출되는 형태입니다.


비록 재정의를 했지만 부모 함수를 사용하고 싶을 때가 있습니다.

class Main():
  def __init__(self):
    pass;
  def _get_data(self):
    return "Main Class";
  def exec_function(self):
    print(self._get_data());
    
class Main2(Main):
  def _get_data(self):
    return "Main2 Class";
  # Main2의 클래스에도 get_data가 있지만 부모 클래스의 get_data를 사용하고 싶을 때는 super()함수를 사용해서 부모함수에 접근할 수 있다.
  def exec_function(self):
    print(super()._get_data());
    
a = Main();
a.exec_function();

b = Main2();
b.exec_function();

위 예제는 Main2함수에서도 get_data함수가 있습니다. 그러나 Main2의 exec_function에서는 super()를 사용하여 부모 클래스, 즉 Main클래스의 get_data를 참조하였습니다.


위 예제들은 모두 Main클래스를 사용하는 클래스이지만, 사용하지 않고 정의만 하는 클래스를 만들 수 있습니다. 이를 추상 클래스라고 합니다.

# 추상 클래스를 사용하기 위해서는 abc 모듈을 가져와야 한다.
from abc import *;

# 추상 클래스는 metaclass=ABCMeta를 넣는다.
class Main(metaclass=ABCMeta):
  # 추상 메서드는 데코레이터 abstractmethod를 지정한다.
  @abstractmethod
  def _get_data(self):
    pass;
  def exec_function(self):
    print(self._get_data());
  
# 추상 클래스를 상속 받았기 때문에 get_data함수를 반드시 재정의 해야 한다. 그렇지 않으면 에러난다.
class Main2(Main):
  def _get_data(self):
    return "Main2 Class";

b = Main2();
b.exec_function();

a = Main();
a.exec_function();

위 결과는 Main2를 호출했을 때는 결과가 잘 출력이 됩니다만, Main을 생성할 때는 에러가 발생합니다.

추상 클래스는 독자적으로 사용할 수는 없습니다.


클래스를 하나만 상속하는 것이 아니고 여러개를 동시에 상속받을 수 있습니다. 이를 다중 상속이라고 합니다.

from abc import *;

class Main1(metaclass=ABCMeta):
  def _get_data1(self):
    return "Main1";
  @abstractmethod
  def exec_function(self):
    pass;
    
class Main2(metaclass=ABCMeta):
  def _get_data2(self):
    return "Main2";
  @abstractmethod
  def exec_function(self):
    pass;

# Main3에서는 Main1과 Main2의 클래스를 동시에 상속 받았습니다.
class Main3(Main1, Main2):
  # Main1의 함수와 Main2의 함수를 동시에 사용할 수 있습니다.
  def exec_function(self):
    print(self._get_data1());
    print(self._get_data2());

b = Main3();
b.exec_function();

그렇다면 다중 상속을 하게 되면 부모 클래스가 같은 함수명을 사용하는 경우가 있습니다. 또는 super로 접근하는데 같은 변수나 함수가 있을 수 있습니다.

from abc import *;

class Main1(metaclass=ABCMeta):
  def _get_data(self):
    return "Main1";
  @abstractmethod
  def exec_function(self):
    pass;
    
class Main2(metaclass=ABCMeta):
  def _get_data(self):
    return "Main2";
  @abstractmethod
  def exec_function(self):
    pass;

class Main3(Main1, Main2):
  def exec_function(self):
    # Main1과 Main2 둘 다 get_data함수가 잇는 상황이다.
    print(super()._get_data());

b = Main3();
b.exec_function();

이 때는 Main3옆에 상속을 선언한 순서가 있는데, 가장 먼저 선언한 것이 우선이 됩니다. Main1를 먼저 상속 받았으니 Main1의 get_data함수를 가져옵니다.

물론 여기서 Main1함수에서 get_data함수가 없다면 Main2의 get_data를 호출하게 됩니다.


다중 상속의 경우는 꽤 유용할 것 같지만 실제 다중 상속이 많아지면 가독성이 매우 안 좋아 집니다. 실제로 많은 클래스를 동시에 상속받고 함수를 호출할 때 어떤 클래스의 함수를 호출하는 지 찾기가 매우 난해합니다.

위처럼 간단한 소스야 문제 없지만 몇 만줄이 넘어가는 소스라고 하면 추적이 매우 어려워 집니다.


참고로 Java나 C#의 경우는 위의 문제가 있기 때문에 자동 상속이 되지 않습니다.


여기까지 Python에서 클래스 상속에 관한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.