누누히 말하지만, Python이 OOP 프로그래밍에 그다지 적합한 언어는 아니다. 덕분에, OOP 프로그래밍을 지원하기 위해 마련된 장치들이 충돌을 일으키는 경우가 종종 있다.
오늘 겪은 문제는 다중상속시 metaclass 충돌 문제다. 먼저 간단하게 말하면, Python이 다중상속을 지원하지만 metaclass를 다중상속 받을 수는 없다는 얘기다. 우회하는 방법이 있는걸로 보이지만, 굳이 이렇게 까지 해야 하나싶고.
문제의 근원인 인터페이스에 대해 먼저 말해보자. Python은 duck typing이라고 해서 같은 메소드를 갖고 있으면 동일하게 취급이 가능하다. 동적타입 언어의 장점으로 볼 수도 있고, 이 자체가 인터페이스의 역할을 하기 때문에 언어레벨에서 인터페이스를 지원하지 않는다.
인터페이스는 아니지만 추상클래스를 간접적으로 지원하는데, 바로 abc 모듈이다. 이를 이용해 인터페이스를 구현할 수 있다. abc는 abstract class를 말한다. abc를 이용하는 방법은 다음과 같다.
from abc import ABCMeta, abstractmethod
class IObserver(metaclass=ABCMeta):
@abstractmethod
def data_update(self, msg: str = ""):
pass
class MainWindow(IObserver):
def data_update(self, msg: str = ""):
print(f"data update : {msg}")
위에서 보듯이, ABCMeta 로 metaclass를 지정해서 추상클래스를 만든다. 추상 메소드는 @abstractmethod 를 붙여준다. concrete class에서 추상 클래스를 상속받고, 동일한 형태의 메소드를 구현해준다. 구현하지 않은 경우, 에러가 발생하게 된다.
여기까지는 매우 행복한 시나리오인데, 내가 쓰는 MainWindow 클래스는 QMainWindow를 상속받아야 한다.
class MainWindow(QMainWindow, IObserver):
...
문제는 Qt에서 사용하는 metaclass와 ABCMeta 두개의 metaclass를 사용하게 되고, 문제가 발생한다.
TypeError: metaclass conflict: the metaclass of a derived class must be ...
해당 문제를 우회하는 방법은 두 type을 받는 새로운 메타클래스를 만드는 것이지만, 굳이 이렇게까지 해야하나 생각이 드는 것.
from PyQt5.QtGui import QStandardItem
from configparser import ConfigParser
class FinalMeta(type(QStandardItem), type(ConfigParser)):
pass
class FinalClass(ConfigParser, QStandardItem, metaclass=FinalMeta):
def __init__(self, param):
ConfigParser.__init__(self)
QStandardItem.__init__(self)
https://stackoverflow.com/questions/28720217/multiple-inheritance-metaclass-conflict 참조.
내가 선택한 해결책은 하나의 metaclass만 사용하는 것이다. abc 모듈을 쓰는게 간편하긴 하지만, 굳이 안쓰고도 interface를 구현할 수 있다. 요점은 interface class에서 구현하지 않되, 이를 상속받아서도 구현하지 않으면 에러를 내는 것이다. 수정한 IObserver는 다음과 같다.
class IObserver:
def data_update(self, msg: str = ""):
raise NotImplementedError
문제 해결! 🙂
Python에서 OOP 프로그래밍을 하면서 계속 벽에 부딪히고 있다. 공부하는 과정에서 전혀 접하지 않았던 문제들이다. 이걸 이렇게 쓰는게 아닌가 싶고, 뭔가 모순적인 감정을 많이 느끼는중.
자바식 인터페이스를 구현하려고 하는 애기입니다, 메타클래스를 그냥 합치면 문제가 해결되는거군요, 감사합니다
예전에 작성했던 포스팅이 도움이 되었다니 다행이네요 ㅎㅎ