{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Effective Python - 59 Specific Ways to Write Better Python. \n", "# *Chapter 4 - Metaclasses and Attributes*\n", "Book by Brett Slatkin. \n", "Summary notes by Tyler Banks." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Metaclasses let you intercept Python's class statement and provide special behavior each time a class is defined" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Item 29: Use Plain Attributes Instead of Get and Set Methods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Conventionally other programming languages use getters and setters" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before: 50000.0\n", "After: 10000.0\n" ] } ], "source": [ "class OldResistor(object):\n", " def __init__(self, ohms):\n", " self._ohms = ohms\n", " def get_ohms(self):\n", " return self._ohms\n", " def set_ohms(self, ohms):\n", " self._ohms = ohms\n", " \n", "r0 = OldResistor(50e3)\n", "print('Before: %5r' % r0.get_ohms())\n", "r0.set_ohms(10e3)\n", "print('After: %5r' % r0.get_ohms())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Python has a more pythonic way of doing this\n", "* You'll implement the `@property` decorator and `setter` attribute" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before: 0 amps\n", "After: 0.01 amps\n" ] } ], "source": [ "class Resistor(object):\n", " def __init__(self, ohms):\n", " self.ohms = ohms\n", " self.voltage = 0\n", " self.current = 0\n", "\n", "class VoltageResistance(Resistor):\n", " def __init__(self, ohms):\n", " super().__init__(ohms)\n", " self._voltage = 0\n", " @property\n", " def voltage(self):\n", " return self._voltage\n", " @voltage.setter\n", " def voltage(self, voltage):\n", " self._voltage = voltage\n", " self.current = self._voltage / self.ohms\n", " \n", "r2 = VoltageResistance(1e3)\n", "print('Before: %5r amps' % r2.current)\n", "r2.voltage = 10\n", "print('After: %5r amps' % r2.current)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "0.000000 ohms must be > 0", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[0mr3\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mBoundedResistance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1e3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m \u001b[0mr3\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mohms\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36mohms\u001b[1;34m(self, ohms)\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mohms\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mohms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mohms\u001b[0m \u001b[1;33m<=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 11\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'%f ohms must be > 0'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mohms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 12\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_ohms\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mohms\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: 0.000000 ohms must be > 0" ] } ], "source": [ "# Using setters to check values and throw exceptions\n", "class BoundedResistance(Resistor):\n", " def __init__(self, ohms):\n", " super().__init__(ohms)\n", " @property\n", " def ohms(self):\n", " return self._ohms\n", " @ohms.setter\n", " def ohms(self, ohms):\n", " if ohms <= 0:\n", " raise ValueError('%f ohms must be > 0' % ohms)\n", " self._ohms = ohms\n", " \n", "r3 = BoundedResistance(1e3)\n", "r3.ohms = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* You could also use `@property` to make attributes immutable\n", "* Define new class interfaces using simple public attributes instead of setters and getters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Item 30: Consider `@property` Instead of Refactoring Attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Use @property to give existing instance attributes new functionallity\n", "* Make incremental progress toward better data models by using `@property`\n", "* Consider refactoring a class and all call sites when you find yourself using `@property` too heavily" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Item 31: Use Descriptors for Reusable `@property` Methods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Problem with `@property` is that the methods it decorates can't be reused for multiple attributes of the same class. They also can't be reused by unrelated classes\n", "* Ex: " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "class Homework(object):\n", " def __init__(self):\n", " self._grade = 0\n", " @property\n", " def grade(self):\n", " return self._grade\n", " @grade.setter\n", " def grade(self, value):\n", " if not (0 <= value <= 100):\n", " raise ValueError('Grade must be between 0 and 100')\n", " self._grade = value" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "person = Homework()\n", "person.grade = 100" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }