{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## 40. super로 부모 클래스를 초기화하라" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "자식 클래스에서 부모 클래스를 초기화하는 오래된 방법은 바로 자식 인스턴스에서 부모 클래스의 \\_\\_init\\_\\_ 메서드를 직접 호출하는 것이다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class MyBaseClass:\n", " def __init__(self, value):\n", " self.value = value" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class MyChildClass(MyBaseClass):\n", " def __init__(self):\n", " MyBaseClass.__init__(self, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 접근 방법은 기본적인 클래스 계층의 경우에는 잘 작동하지만, 다른 경우에는 잘못될 수도 있다.\n", "\n", "다중 상속에 의해 영향을 받은 경우 예측할 수 없는 방식으로 작동할 수 있다.\n", "\n", "다중 상속을 사용하는 경우 생기는 문제 중 하나는 모든 하위 클래스에서 \\_\\_init\\_\\_호출의 순서가 정해져 있지 않다는 것이다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class TimesTwo:\n", " def __init__(self):\n", " self.value *= 2" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class PlusFive:\n", " def __init__(self):\n", " self.value += 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다음 클래스 정의는 부모 클래스를 TimesTwo, PlusFive 순서로 정의한다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "class OneWay(MyBaseClass, TimesTwo, PlusFive):\n", " def __init__(self, value):\n", " MyBaseClass.__init__(self, value)\n", " TimesTwo.__init__(self)\n", " PlusFive.__init__(self)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 클래스의 인스턴스를 만들면 부모 클래스의 순서에 따라 초기화가 실행되는 결과를 볼 수 있다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "첫 번째 부모 클래스 순서에 따른 값은 (5 * 2) + 5 = 15\n" ] } ], "source": [ "foo = OneWay(5)\n", "print('첫 번째 부모 클래스 순서에 따른 값은 (5 * 2) + 5 =', foo.value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다음은 부모 클래스 순서를 다르게 나열한 경우다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "class AnotherWay(MyBaseClass, PlusFive, TimesTwo):\n", " def __init__(self, value):\n", " MyBaseClass.__init__(self, value)\n", " TimesTwo.__init__(self)\n", " PlusFive.__init__(self)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 부모 클래스의 생성자 순서는 그대로라서 똑같은 결과를 볼 수 있다.\n", "\n", "즉 클래스 정의에서 부모클래스를 나열한 순서에 따르지 않는다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "15\n" ] } ], "source": [ "bar = AnotherWay(5)\n", "print(bar.value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다이아몬드 상속으로 문제가 생길 수 있다.\n", "\n", "다이아몬드 상속은 공통 조상의 \\_\\_init\\_\\_ 메서드를 여러번 호출 될 수 있기 때문이다. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class TimesSeven(MyBaseClass):\n", " def __init__(self, value):\n", " MyBaseClass.__init__(self, value)\n", " self.value *= 7" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "class PlusNine(MyBaseClass):\n", " def __init__(self, value):\n", " MyBaseClass.__init__(self, value)\n", " self.value += 9" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "class ThisWay(TimesSeven, PlusNine):\n", " def __init__(self, value):\n", " TimesSeven.__init__(self, value)\n", " PlusNine.__init__(self, value)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(5 * 7) + 9 = 44 가 나와야하지만 실제는 14\n" ] } ], "source": [ "foo = ThisWay(5)\n", "print('(5 * 7) + 9 = 44 가 나와야하지만 실제는 ', foo.value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "두 번째 부모 클래스의 생성자 PlusNine.\\_\\_init\\_\\_을 호출하면 MyBaseClas.\\_\\_init\\_\\_이 다시 호출되면서 self.value가 5로 돌아간다.\n", "\n", "따라서 self.value는 5 + 9 = 14가 된다.\n", "\n", "이러한 문제를 해결하기 위해 파이썬에는 super라는 내장 함수와 표준 메서드 결정 순서 (MRO) 가 있다.\n", "\n", "super를 사용하면 다이아몬드 계층의 공통 상위 클래스를 단 한번만 호출하도록 보장한다.\n", "\n", "MRO는 상위 클래스를 초기화하는 순서를 정의한다.\n", "\n", "이때 C3 선형화라는 알고리즘을 사용한다.\n", "\n", "다음 코드는 다이아몬드 모양의 클래스 구조를 다시 만들되, super를 사용해 부모 클래스를 초기화한다." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "tags": [] }, "outputs": [], "source": [ "class TimesSevenCorrect(MyBaseClass):\n", " def __init__(self, value):\n", " super().__init__(value)\n", " self.value *= 7" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "class PlusNineCorrect(MyBaseClass):\n", " def __init__(self, value):\n", " super().__init__(value)\n", " self.value += 9" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이제는 다이아몬드의 정점에 있는 부모 클래스는 한번만 실행되고, 생성자는 class 문에 지정된 순서대로 호출 된다." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "class GoodWay(TimesSevenCorrect, PlusNineCorrect):\n", " def __init__(self, value):\n", " super().__init__(value)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "98\n" ] } ], "source": [ "foo = GoodWay(5)\n", "print(foo.value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "7 * 5 + 9로 생각할 수 있지만 생성자 순서는 MRO 정의에 따른다" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "mro_str = '\\n'.join(repr(cls) for cls in GoodWay.mro())" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<class '__main__.GoodWay'>\n", "<class '__main__.TimesSevenCorrect'>\n", "<class '__main__.PlusNineCorrect'>\n", "<class '__main__.MyBaseClass'>\n", "<class 'object'>\n" ] } ], "source": [ "print(mro_str)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "역순으로 생성자가 호출된다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "super().\\_\\_init\\_\\_ 호출은 다중 상속을 튼튼하게 해주며, 하위 클래스에서 MyBaseClass.\\_\\_init\\_\\_을 직접 호출하는 것보다 유지보수를 더 편하게 해준다.\n", "\n", "나중에 클래스 이름을 바꿔도 super로 호출할 수 있다.\n", "\n", "또한, super 함수에 두 가지 파라미터를 넘길 수 있다. 첫번째 파라미터는 여러분이 접근하고 싶은 MRO 뷰를 제공할 부모 타입이고, 투번째 파라미터는 첫 번쨰 파라미터로 지정한타입의 MRO 뷰에 접근할 떄 사용할 인스턴스다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "class ExplicitTrisect(MyBaseClass):\n", " def __init__(self, value):\n", " super(ExplicitTrisect, self).__init__(value)\n", " self.value /= 3 " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하지만 object 인스턴스를 초기화할 때는 두 파라미터를 지정할 필요가 없다.\n", "\n", "클래스 정의 안에서 아무 인자도 지정하지 않고 super를 호출하면, 파이썬 컴파일러가 자동으로 올바른 파라미터(\\_\\_class\\_\\_와 self)를 넣어준다.\n", "\n", "따라서 앞과 다음 두가지는 같다." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "class AutomaticTrisect(MyBaseClass):\n", " def __init__(self, value):\n", " super(__class__, self).__init__(value)\n", " self.value /= 3 " ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "class ImplcitTrisect(MyBaseClass):\n", " def __init__(self, value):\n", " super().__init__(value)\n", " self.value /= 3 " ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.0" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ExplicitTrisect(9).value" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.0" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "AutomaticTrisect(9).value" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.0" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ImplcitTrisect(9).value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "super에 파라미터를 제공해야 하는 유일한 경우는 자식 클래스에서 상위 클래스의 특정 기능에 접근해야하는 경우뿐이다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 기억해야 할 내용\n", "- 파이썬은 표준 메서드 결정 순서(MRO)를 활용해 상위 클래스 초기화 순서와 다이아몬드 상속 문제를 해결한다.\n", "- 부모 클래스를 초기화할 때는 super 내장 함수를 아무 인자 없이 호출하라. super를 아무 인자 없이 호출하면 파이썬 컴파일러가 자동으로 올바른 파라미터를 넣어준다." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }