{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. 메타클래스와 애트리뷰트\n",
    "\n",
    "메타클래스를 사용하면 파이썬의 class 문을 가로채서 클래스가 정의될 떄마다 특별한 동작을 제공할 수 있다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 44. 세터와 게터 메서드 대신 평범한 애트리뷰트를 사용하라"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다른 언어를 사용하다 파이썬을 접한 프로그래머들은 게터와 세터를 명시적으로 정의한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class OldResistor:\n",
    "    def __init__(self, ohms):\n",
    "        self._ohms = ohms\n",
    "\n",
    "    def get_ohms(self):\n",
    "        return self._ohms\n",
    "\n",
    "    def set_ohms(self, ohms):\n",
    "        self._ohms = ohms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 파이썬 답지 않다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이전: 50000.0\n",
      "이후: 10000.0\n"
     ]
    }
   ],
   "source": [
    "r0 = OldResistor(50e3)\n",
    "print('이전:', r0.get_ohms())\n",
    "r0.set_ohms(10e3)\n",
    "print('이후:', r0.get_ohms())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "특히 필드 값을 증가시키는 연산 등의 경우에는 이런 메서드를 사용하면 코드가 지저분해 진다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "r0.set_ohms(r0.get_ohms() - 4e3)\n",
    "assert r0.get_ohms() == 6e3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬에서는 단순한 공개 애트리뷰트로부터 구현을 시작하라"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Resistor:\n",
    "    def __init__(self, ohms):\n",
    "        self.ohms = ohms\n",
    "        self.voltage = 0\n",
    "        self.current = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "r1 = Resistor(50e3)\n",
    "r1.ohms = 10e3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "애트리뷰트를 사용하면 제자리에서 증가시키는 등이 연산이 더 자연스럽고 명확해진다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "r1.ohms += 5e3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "나중에 애트리뷰트가 설정될 떄 특별한 기능을 수행해야 한다면, 애트리뷰트를 @property 데코레이터와 대응하는 setter 애트리뷰트로 옮겨갈 수 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class VoltageResistance(Resistor):\n",
    "    def __init__(self, ohms):\n",
    "        super().__init__(ohms)\n",
    "        self._voltage = 0\n",
    "\n",
    "    @property\n",
    "    def voltage(self):\n",
    "        return self._voltage\n",
    "\n",
    "    @voltage.setter\n",
    "    def voltage(self, voltage):\n",
    "        self._voltage = voltage\n",
    "        self.current = self._voltage / self.ohms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이제 voltage 프로퍼티에 대입하면 voltage 세터 메서드가 호출되고, 이 메서드는 객체의 current 애트리뷰트를 변경된 전압 값에 맞춰 갱신한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이전: 0.00 암페어\n",
      "이후: 0.01 암페어\n"
     ]
    }
   ],
   "source": [
    "r2 = VoltageResistance(1e3)\n",
    "print(f'이전: {r2.current:.2f} 암페어')\n",
    "r2.voltage = 10\n",
    "print(f'이후: {r2.current:.2f} 암페어')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "프로퍼티에 대해 setter를 지정하면 타입을 검사하거나 클래스 프로퍼티에 전달된 값에 대한 검증을 수행할 수 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BoundedResistance(Resistor):\n",
    "    def __init__(self, ohms):\n",
    "        super().__init__(ohms)\n",
    "\n",
    "    @property\n",
    "    def ohms(self):\n",
    "        return self._ohms\n",
    "\n",
    "    @ohms.setter\n",
    "    def ohms(self, ohms):\n",
    "        if ohms <= 0:\n",
    "            raise ValueError(f'저항 > 0이어야 합니다. 실제 값: {ohms}')\n",
    "        self._ohms = ohms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "잘못된 저항 값을 대입하면 예외가 발생한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "r3 = BoundedResistance(1e3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "저항 > 0이어야 합니다. 실제 값: 0",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-13-028e916e3f75>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mr3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-10-71ca84303584>\u001b[0m in \u001b[0;36mohms\u001b[0;34m(self, ohms)\u001b[0m\n\u001b[1;32m     10\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mohms\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m             \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'저항 > 0이어야 합니다. 실제 값: {ohms}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     13\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_ohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: 저항 > 0이어야 합니다. 실제 값: 0"
     ]
    }
   ],
   "source": [
    "r3.ohms = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "생성자에 잘못된 값을 넘기는 경우에도 예외가 발생한다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "저항 > 0이어야 합니다. 실제 값: -5",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-14-9fc5cf3426e1>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mBoundedResistance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-10-71ca84303584>\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, ohms)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mBoundedResistance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mResistor\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      2\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m         \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-5-299937d78e30>\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, ohms)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mResistor\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      2\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      4\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvoltage\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcurrent\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-10-71ca84303584>\u001b[0m in \u001b[0;36mohms\u001b[0;34m(self, ohms)\u001b[0m\n\u001b[1;32m     10\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mohms\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m             \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'저항 > 0이어야 합니다. 실제 값: {ohms}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     13\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_ohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: 저항 > 0이어야 합니다. 실제 값: -5"
     ]
    }
   ],
   "source": [
    "BoundedResistance(-5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "심지어 @property를 사용해 부모 클래스에 정의된 애트리뷰트를 불변으로 만들 수도 있다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class FixedResistance(Resistor):\n",
    "    def __init__(self, ohms):\n",
    "        super().__init__(ohms)\n",
    "\n",
    "    @property\n",
    "    def ohms(self):\n",
    "        return self._ohms\n",
    "\n",
    "    @ohms.setter\n",
    "    def ohms(self, ohms):\n",
    "        if hasattr(self, '_ohms'):\n",
    "            raise AttributeError(\"Ohms는 불변객체입니다\")\n",
    "        self._ohms = ohms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "r4 = FixedResistance(1e3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "Ohms는 불변객체입니다",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-17-9addc5737525>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mr4\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2e3\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-15-c536541bb2ce>\u001b[0m in \u001b[0;36mohms\u001b[0;34m(self, ohms)\u001b[0m\n\u001b[1;32m     10\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'_ohms'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m             \u001b[0;32mraise\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Ohms는 불변객체입니다\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     13\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_ohms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mohms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mAttributeError\u001b[0m: Ohms는 불변객체입니다"
     ]
    }
   ],
   "source": [
    "r4.ohms = 2e3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "@property 메서드를 사용해 세터와 게터를 구현할 떄는 게터나 세터 구현이 예기치 않은 동작을 수행하지 않도록 만들어야 한다.\n",
    "\n",
    "예를 들어 게터 프로퍼티 메서드 안에서 다른 애트리뷰트를 설정하면 안된다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MysteriousResistor(Resistor):\n",
    "    @property\n",
    "    def ohms(self):\n",
    "        self.voltage = self._ohms * self.current\n",
    "        return self._ohms\n",
    "\n",
    "    @ohms.setter\n",
    "    def ohms(self, ohms):\n",
    "        self._ohms = ohms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이전: 0.00\n",
      "이후: 0.10\n"
     ]
    }
   ],
   "source": [
    "r7 = MysteriousResistor(10)\n",
    "r7.current = 0.01\n",
    "print(f'이전: {r7.voltage:.2f}')\n",
    "r7.ohms\n",
    "print(f'이후: {r7.voltage:.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "게터나 세터를 정의할 때 가장 좋은 정책은 관려이 있는 객체 상태를 @property.setter 메서드 안에서만 변경하는 것이다.\n",
    "\n",
    "@property의 가장 큰 단점은 애트리뷰트를 처리하는 메서드가 하위 클래스 사이에서만 공유될 수 있다는 것이다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 기억해야 할 내용\n",
    "- 새로운 클래스 인터페이스를 정의할 떄는 간단한 공개 애트리뷰트에서 시작하고, 세터나 게터 메서드를 가급적 사용하지 말라.\n",
    "- 객체에 있는 애트리뷰트에 접근할 때 특별한 동작이 필요하면 @property로 이를 구현 할 수 있다.\n",
    "- @property 메서드를 만들 때는 최소 놀람의 법칙을 따르고 이상한 부작용을 만들어 내지 말라\n",
    "- @property 메서드가 빠르게 실행되도록 유지하라. 느리거나 복잡한 작업의 경우(특히 I/O를 수행하는 등의 부수 효과가 있는 경우)에는 프로퍼티 대신 일반적인 메서드를 사용하라."
   ]
  }
 ],
 "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
}