{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Extending Types by Subclassing\n", "Allows you to customize or extend the behavior of built-in types with user-defined class" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Content: \n", "[1, 2, 3]\n" ] } ], "source": [ "class MyList(list):\n", " def __str__(self):\n", " print(\"Content: \")\n", " return list.__str__(self)\n", "\n", "\n", "ml = MyList([1, 2, 3])\n", "print(ml)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The \"New Style\" Class Model\n", "\n", "- In Python3, all classes are \"new style\"\n", "- In Python2, only explicitly inheritence from **object** would be regarded as \"new style\"" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class C(object):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## New-Style Class Changes\n", "### 1. Attribute fetch for built-ins: instance skipped\n", "- **`__getattr__`**, **`__getattribute__`** can no longer be called by other built-in operations for **`__X__`**\n", "- The search for such names begins at classes, not instances" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for +: 'GetAttrTest' and 'int'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\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 8\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__add__\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mlambda\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m88\u001b[0m\u001b[1;33m+\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 10\u001b[1;33m \u001b[0mg\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'GetAttrTest' and 'int'" ] } ], "source": [ "class GetAttrTest(object):\n", " data = \"spam\"\n", "\n", " def __getattr__(self, attrname):\n", " print(attrname)\n", " return getattr(self.data, attrname)\n", "\n", "\n", "g = GetAttrTest()\n", "\n", "g.__add__ = lambda y: 88 + y\n", "g + 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Direct calls to built-in method names still work, but equivalent expression is not" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "89" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Direct calls still work\n", "\n", "g.__add__(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The effect\n", "\n", "To code a proxy of an object whose interface may in part be invoked by built-in operations, new style classes require both **`--getattr__`** for normal names, as well as method redefinitions for all names accessed by built-in operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Classes and types merges\n", "**type(I)** return the class an instance is made from, instead of generic instance type" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "int" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- type checking is ususally not recommended in Python\n", " - **isinstance** might be prefered, but still not recommened" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(5, (int, str))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "type and class hasve merged - type is a kind of object while object is a kind of type" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(type, object)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(object, type)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Automatic object root class\n", "A small set of default operator overloading method (e.g. `__repr__`)\n", "\n", "## 4. Inheritance search order: MRO and diamonds (Also mentioned in Ch31)\n", "- Classic Class (Python2) -> DFLR \n", "- New-style Class (Python3) -> MRO\n", " - New-Style class inheritance works the same for most other inheritance tree structures\n", "\n", "### Pros of MRO\n", "- Avoids visiting the same superclass more than once when it's accessible from multiple subclasses (performance optimization)\n", "- Without the new-style MRO, **object** would always override redefinitions in user-coded classes" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\r\n" ] } ], "source": [ "%%python2\n", "# To make this cell magic runs ok please ensure python2 can be run when you type python2 in terminal\n", "# Reference: http://stackoverflow.com/questions/30201431/ipython-cell-magics\n", "\n", "# DFLR: D -> B -> A -> C -> A\n", "\n", "class A: attr =1\n", "class B(A): pass\n", "class C(A): attr = 2\n", "class D(B, C): pass\n", "\n", "x = D()\n", "print(x.attr) # x -> D -> B -> A" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "# MRO: D -> B -> C -> A\n", "\n", "\n", "class A:\n", " attr = 1\n", "\n", "\n", "class B(A):\n", " pass\n", "\n", "\n", "class C(A):\n", " attr = 2\n", "\n", "\n", "class D(B, C):\n", " pass\n", "\n", "\n", "x = D()\n", "print(x.attr) # x -> D -> B -> C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can simply resolve this by using **`attr = C.attr`** in in class D " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\r\n" ] } ], "source": [ "%%python2\n", "\n", "class A: attr =1\n", "class B(A): pass\n", "class C(A): attr = 2\n", "class D(B, C): attr = C.attr\n", "\n", "x = D()\n", "print(x.attr) # x -> D -> B -> A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `__mro__`" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(__main__.D, __main__.B, __main__.C, __main__.A, object)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class A:\n", " attr = 1\n", "\n", "\n", "class B(A):\n", " pass\n", "\n", "\n", "class C(A):\n", " attr = 2\n", "\n", "\n", "class D(B, C):\n", " pass\n", "\n", "\n", "D.__mro__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How MRO works\n", "1. List all the classes using the classic class's DFLR and include a class multiple times if it's vistied more than once\n", "2. Scan the resulting list for duplicate classes, remove all but the last occurrence of the duplicates in the list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Inhertitance algorithm\n", "Will be mentioned in Ch40\n", "\n", "## 6. New advanced tools\n", "\n", "### slot: Attribute Declarations\n", "- **slots should be used only in applications that clearly warrant the added complexity**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Basic Example" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "age", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\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 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mx\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlimiter\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mx\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mage\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: age" ] } ], "source": [ "class limiter(object):\n", " __slots__ = [\"age\", \"name\", \"job\"]\n", "\n", "\n", "x = limiter()\n", "x.age" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "40\n" ] } ], "source": [ "x.age = 40 # must be assign first\n", "print(x.age)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'limiter' object has no attribute 'ape'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mx\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mape\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1000\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: 'limiter' object has no attribute 'ape'" ] } ], "source": [ "x.ape = 1000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- To save space, instead of allocating dictionary for each instance, Python reserves just enough space in each instance to hold a value for each slot attribute, along with inherited attributes in the common class to manage slot access \n", " - Best reserved for rare cases where there are large number of instances in memory-critical applications\n", " - In Python3.3, non-slots attribute space requirements have been reduced with a key-sharing dictionay model, where the **`__dict__`** used for objects' attributes may share part of their internal storage, including that of their keys. This may lessen some of the value of **`__slots__`** as an optimization tool" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Classes with **`__slots__`** do not have **`__dict__`** by defualt" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'C' object has no attribute '__dict__'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\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 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mC\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mc\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__dict__\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: 'C' object has no attribute '__dict__'" ] } ], "source": [ "class C(object):\n", " __slots__ = [\"a\"]\n", "\n", "\n", "c = C()\n", "c.__dict__" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['__class__',\n", " '__delattr__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__getattribute__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__le__',\n", " '__lt__',\n", " '__module__',\n", " '__ne__',\n", " '__new__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__slots__',\n", " '__str__',\n", " '__subclasshook__',\n", " 'a']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dir(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- However, **`__dict__`** can still be included in **`__slots__`**" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'d': 5, 'e': 5}" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class C(object):\n", " __slots__ = [\"a\", \"b\", \"__dict__\"]\n", " d = 3\n", "\n", " def __init__(self):\n", " self.d = 5\n", " self.a = 1\n", "\n", "\n", "c = C()\n", "c.e = 5\n", "c.__dict__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Without an attribute namespace dict, it's not possible to assign new names to instances that are not in slots list" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'C' object has no attribute 'd'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\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 3\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0md\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m4\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mC\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0m__slots__\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;34m'a'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0md\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m4\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[0mc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mC\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mAttributeError\u001b[0m: 'C' object has no attribute 'd'" ] } ], "source": [ "class C(object):\n", " __slots__ = [\"a\", \"b\"]\n", "\n", " def __init__(self):\n", " self.d = 4\n", "\n", "\n", "c = C()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Slot Usage Rules\n", "\n", "- Slots in subs are pointless when absent in supers\n", " - **`__dict__`** created for the superclass will always be accessible\n", " - Subclass still manages its slots, but doesn't compute their values in anyway, and doesn't avoid a dict\n", "- Slots in supers are pointless when absent in subs\n", " - Subclasses will produce and instacne **`__dict__`**\n", "- Redefintion renders super slots pointless\n", " - A class defines the same slot name as a superclass, its redefinition hides the slot in superclass\n", "- Slots prevent class-level defaults\n", " - Class attributes of the same name to provide default cannot be uesd\n", "- Slots and **`__dict__`**\n", " - Slots preclude **`__dict__`**, unless it's listed explicitly\n", " \n", "```\n", "slots essentially require both universal and careful deployment to be effective\n", "slots are not generally recommended, except in pathologicall cases where their space reduction is significant\n", "```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### Properties: Attribute Accessors\n", "- Similar to proerties in languages like Java and C#\n", "- proerties inercept accesss and compute values arbitrarily\n", "\n", "\n", "#### Basics\n", "- A property is a type of object assigned to a class attribute name\n", "- By calling **property** and passing in up to three accessor methods (handlers for get, set and delete)\n", " - If any of them is None or omitted, then that operation is not supported\n", " - The result property object is typically assigned to a name at the top level of a class\n", " - After thus assigned, accesses to the class property name are automatically routed to one of the accessor methods\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Proper(object):\n", " def getage(self):\n", " return 40\n", "\n", " age = property(getage, None, None, None) # (get, set, del, docs)\n", "\n", "\n", "p = Proper()\n", "p.age" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `__getattribute__` and Descriptors\n", "\n", "- **`__getattribute__`** is available for new-style classes only and used to intercept **all** attribute\n", " - It's prone to loops much like **`__setattr__`**\n", "- Python supports the notion of attribute descriptors - classes with **`__get__`** and **`__set__`**" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "30" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class AgeDesc(object):\n", " def __get__(self, instance, owner):\n", " return 30\n", "\n", " def __set__(self, instance, value):\n", " instance._age = value\n", "\n", "\n", "class D(object):\n", " age = AgeDesc()\n", "\n", "\n", "d = D()\n", "d.age" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d.age = 42\n", "d._age" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Static and Class Methods\n", "\n", "## Static Methods\n", "### Static Methods in Python2 and Python3\n", "\n", "- To call a method without instance\n", " - In Python2, **staticmethod** is a must\n", " - In Python3, **staticmethod** is needed only if it would be called through an instance" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "# Works both Python2 and Python3\n", "\n", "\n", "class C(object):\n", " @staticmethod\n", " def print_one():\n", " print(1)\n", "\n", "\n", "c = C()\n", "c.print_one()" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "# Python3 Only\n", "\n", "\n", "class C(object):\n", " def print_one():\n", " print(1)\n", "\n", "\n", "C.print_one()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Look at the code above. \n", "Since **print_one** does not operate any class or instance data, it may be a good idea to make it static" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Class Method\n", "- Methods of a class that are passed a class object in their first argument instead of an instance, regardless of whether they are called through an instance or a class\n", "\n", "- Receive the lowest class of the call's subject" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n" ] } ], "source": [ "class C(object):\n", " counter = 0\n", "\n", " @classmethod\n", " def cmethod(cls):\n", " cls.counter += 1\n", " print(cls.counter)\n", "\n", "\n", "c1 = C()\n", "c1.cmethod()\n", "\n", "c2 = C()\n", "c2.cmethod()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Static Method VS Class Method\n", "\n", "- static method: process data local to a class\n", "- class method: process data that may differ for each class in hierarchy\n", " - code that needs to manage per-class instance counters, for example, might be best off leveragin class method" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Why using static/class method instead of simple function?\n", "- Localized the function name in class scope\n", "- Move the function closer to where it's used\n", "- Allows subclasses to customize" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Decorators and Metaclasses (Detail in Ch39, Ch40)\n", "- Decorators\n", " - General tool for adding logic that manages both functions and classes, or later calls to them\n", " - e.g. log, count calls, check its argument types\n", "\n", "## Function decorator\n", "- Augment function definitions\n", "- Wrap class method in an extra layer of logic implemented as another function, usually called metafunction\n", "- Similar to delegation design pattern but designed to augment a specific funtcion\n", "- A sort of runtime declaration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What the function decorators do" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Decorator version\n", "\n", "\n", "class C:\n", " @staticmethod\n", " def meth():\n", " pass" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class C:\n", " def meth():\n", " pass\n", "\n", " meth = staticmethod(meth)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### User-Defined Function Decotator\n", "Use **`__call__`**" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Call 1 to spam\n", "6\n", "Call 2 to spam\n", "12\n" ] } ], "source": [ "class tracer:\n", " def __init__(self, func):\n", " self.calls = 0\n", " self.func = func\n", "\n", " def __call__(self, *args):\n", " self.calls += 1\n", " print(\"Call {} to {}\".format(self.calls, self.func.__name__))\n", " return self.func(*args)\n", "\n", "\n", "@tracer\n", "def spam(a, b, c): # Same as spam=tracer(spam)\n", " return a + b + c\n", "\n", "\n", "print(spam(1, 2, 3))\n", "print(spam(3, 4, 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Explanation\n", "- The **spam** function is run through the **tracer** decorator, when the original **spam** is called it actually triggers the **`__call__`** in the class\n", "\n", "- **`*name`** argument is used to pack and unpack the passed-in arguments, because of this, this decorator can be used to wrap any function with any number of positional arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Class dectorator and metaclass\n", "- Augment class definitions\n", "- Add supoort for management of whole objects and their interface (metaclass)\n", "- Manage an object's entire interface by intercepting construction calls\n", "- metaclass generally redeines the **`__new__`** or **`__init__`** of the **type** class that normally intercepts this call\n", " - meta class need not be a class at all" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What the class decorators do" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def decorator(a_class):\n", " pass\n", "\n", "\n", "@decorator\n", "class C(object):\n", " pass" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def decoraor(a_class):\n", " pass\n", "\n", "\n", "class C(object):\n", " pass\n", "\n", "\n", "C = decoraor(C)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "# Super\n", "\n", "Skip\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Class Gotachas\n", "\n", "## Class Are Mutable Objects (Side Effect)\n", "- Chaning class attributes can have side effects" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "class X(object):\n", " a = 1\n", "\n", "\n", "X.a = 2\n", "x2 = X()\n", "print(x2.a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiple Inheritance: Order Matters\n", "- Python always searches superclasses from left to right\n", " - As a rule of thumb, multiple inheritance works best when mix-in classes are as self-contained as possible" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Other Issue\n", "- Choose per-instance or class storage wisely\n", "- You usually want to call superclass constructors" ] } ], "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.0" } }, "nbformat": 4, "nbformat_minor": 1 }