Skip to content

Python:Mixin

Python 에서 Mixin 구현 방법.

Mixin 클래스의 자동 초기화 방법

다음은 정상적으로 작동하지 않는 코드이다:

class _Test0:

    value0: list

    def __init__(self):
        print("_Test0.__init__")
        self.value0 = list()


class _Test1:

    value1: dict

    def __init__(self):
        print("_Test1.__init__")
        self.value1 = dict()


class _Test2(_Test0, _Test1):
    pass


a = _Test2()
print(a.value0)
print(a.value1)  # AttributeError

_Test0.__init__는 호출되지만, _Test1.__init__은 호출되지 않는다. Python 에서는 다중 상속 시 명시되지 않는 한 첫 번째 상속 클래스의 초기화만 사용한다.

따라서 Mixin 클래스를 사용하고 싶을 경우, 다음과 같이 __new__ 함수를 구현하면 된다:

# -*- coding: utf-8 -*-

numbering = 0


class _TestBase:
    __uid: int
    _module: int

    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls)
        global numbering
        instance.uid = numbering
        numbering += 1
        print(f"_TestBase.__new__(uid={instance.uid})")
        return instance


class _Test0(_TestBase):

    value0: list

    def __new__(cls, *args, **kwargs):
        print("_Test0.__new__")
        instance = super().__new__(cls)
        instance.value0 = list()
        return instance


class _Test1(_TestBase):

    value1: dict

    def __new__(cls, *args, **kwargs):
        print("_Test1.__new__")
        instance = super().__new__(cls)
        instance.value1 = dict()
        return instance


class _Test2(_Test0, _Test1):
    def __new__(cls, *args, **kwargs):
        print("_Test2.__new__")
        return super().__new__(cls, *args, **kwargs)

    def __init__(self):
        pass


print("new a")
a = _Test2()
print(a.value0)
print(a.value1)

print("new b")
b = _Test2()
print("-------")
print(b.value0)
print(b.value1)
# print(b.__uid)
print("=======")

a.value0.append(0)
a.value1.setdefault("a", 100)
b.value0.append(1)
b.value1.setdefault("b", 200)
print(a.value0)
print(a.value1)
print("-------")
print(b.value0)
print(b.value1)

print("=======")
try:
    print(_Test2.value0)
except AttributeError:
    print("_Test2.value0 AttributeError")

try:
    print(_Test2.value1)
except AttributeError:
    print("_Test2.value0 AttributeError")

참고로 출력은 다음과 같다:

new a
_Test2.__new__
_Test0.__new__
_Test1.__new__
_TestBase.__new__(uid=0)
[]
{}
new b
_Test2.__new__
_Test0.__new__
_Test1.__new__
_TestBase.__new__(uid=1)
-------
[]
{}
=======
[0]
{'a': 100}
-------
[1]
{'b': 200}
=======
_Test2.value0 AttributeError
_Test2.value0 AttributeError

보면 알겠지만 다이아몬드 상속 구조 이다.

    _TestBase
     /     \
 _Test0   _Test1
     \     /
     _Test2

이 경우에 최 상단에 있는 _TestBase 클래스의 __new__ 함수가 한 번만 호출된 것을 알 수 있다.

See also