{
 "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
}