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