{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Metaclass\n", "- Meta classes allow us to intercept and augment class creation\n", "\n", "## Usage\n", "- Add decoration to all methods of classes\n", "- Register all classes in use to an API\n", "- User Interface logic\n", "- Create or extend classes from simplified specifactions in text files\n", "\n", "## Metaclass VS Class Decorator\n", "- Class decorator is designed to manage instance\n", "- Metaclass are designed to augment class construction\n", "- The main differnece difference between class decorators and metaclass is their place in the timing of class creation\n", "\n", "## Metaclass VS Helper Function\n", "- The advantage of metaclass\n", " - explicit structure\n", " - consistentcy\n", " - avoid code redundancy\n", " \n", "### Example\n", "Add a extra function to a class" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Function to add\n", "def extra(self, arg):\n", " print(\"[From extra]: \", arg)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Version 1: Helper Function\n", "def extras(cls):\n", " cls.extra = extra\n", "\n", "\n", "class C1:\n", " pass\n", "\n", "\n", "extras(C1)\n", "\n", "\n", "class C2:\n", " pass\n", "\n", "\n", "extras(C2)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Version 2: Metaclass\n", "\n", "\n", "class Extras(type):\n", " def __init__(cls, clsname, superclasses, attributedict):\n", " cls.extra = extra\n", "\n", "\n", "class C1(metaclass=Extras):\n", " pass\n", "\n", "\n", "class C2(metaclass=Extras):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Class Statement Protocol\n", "\n", "- **type** is a class that generates user-defined classes\n", "- Metaclasses are subclasses of the **type** class\n", "- Class objects are instnces of the **type** class, or a subclass\n", "- Instance objects are generated form a class\n", "\n", "### Behind Class Statement\n", "```python\n", "class = type(classname, superclasses, attributeddict)\n", "```\n", "\n", "**type** defines a **`__call__`** and runs the following methods\n", "```python\n", "type.__new__(typeclass, classname, superclasses, attributedict)\n", "type.__init__(cls, classname, superclasses, attributedict)\n", "```\n", "\n", "\n", "The following two cells are equivalent" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class A:\n", " pass\n", "\n", "\n", "class B(A):\n", " data = 1\n", "\n", " def meth(self, arg):\n", " return self.data + arg\n", "\n", "\n", "b = B()\n", "b.meth(2)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B = type(\"B\", (A,), {\"data\": 1, \"meth\": (lambda x, y: x.data + y)})\n", "\n", "b = B()\n", "b.meth(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Basic Idea of Metaclasses\n", "Thus, to control the way classes are created and augment their behavior. \n", "We have to specify that a user-defined class be created from a user-defined metaclass instead of the normal **type** class." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Declaration of Metaclasses\n", "- Superclasses must be listed before the metaclass\n", "- There is no simple protability between version\n", " - Python3 ignores the Python2 `__metaclass__`\n", " - Python2 regards the metaclass keyword as syntax error\n", "\n", "### Python3\n", "```python\n", "class A(metaclass=Meta):\n", " pass\n", " \n", "class B(C, metaclass=Meta):\n", " pass\n", "```\n", "\n", "### Python2\n", "```python\n", "class A(object):\n", " __metaclass = Meta\n", " \n", "class B(C, object):\n", " __metaclass__ = Meta\n", "```\n", "\n", "Although classes derived from **object** explicityly is not a must in Python2, the `__metaclass__` declaration makes the resulting class new-style. \n", "Thus, it's suggested to explicitly declared\n", "\n", "### Behind The Declaration\n", "Replace the *type* with *metaclass* when creating class\n", "\n", "```python\n", "class = Meta(clsname, superclasses, attributedict)\n", "```\n", "\n", "Because metaclass is a subclass of type, the **`__call__`** is delegated\n", "```python\n", "Meta.__new__(Meta, clsname, superclasses, attributedict)\n", "Meta.__init__(cls, classname, superclasses, attributedict)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implement Metaclasses\n", "\n", "### A Baic Metaclass" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---Create class B---\n", "Meta New: \n", "\t...\n", "\t...B\n", "\t...(,)\n", "\t...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': , 'meth': }\n", "Meta New: \n", "\t...\n", "\t...B\n", "\t...(,)\n", "\t...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': , 'meth': }\n", "---Create instance b---\n", "Create instance of B\n" ] } ], "source": [ "class Meta(type):\n", " def __new__(meta, classname, supers, classdict):\n", " print(\"Meta New: \", meta, classname, supers, classdict, sep=\"\\n\\t...\")\n", " return type.__new__(meta, classname, supers, classdict)\n", "\n", " def __init__(cls, classname, supers, classdict):\n", " print(\"Meta New: \", cls, classname, supers, classdict, sep=\"\\n\\t...\")\n", "\n", "\n", "class A:\n", " pass\n", "\n", "\n", "print(\"---Create class B---\")\n", "\n", "\n", "class B(A, metaclass=Meta):\n", " data = 1\n", "\n", " def __init__(self):\n", " print(\"Create instance of B\")\n", "\n", " def meth(self, arg):\n", " return self.data + arg\n", "\n", "\n", "print(\"---Create instance b---\")\n", "b = B()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other Metaclass Techniques\n", "Metaclasses need not really be classes, it can be any callable object that accepts the arguments passed and returns an object compatible with the intended class" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---Create class B---\n", "MetaFunc: \n", "\t...B\n", "\t...(,)\n", "\t...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': , 'meth': }\n", "---Create instance b---\n", "Create instance of B\n" ] } ], "source": [ "def MetaFunc(clsname, supers, clsdict):\n", " print(\"MetaFunc: \", clsname, supers, clsdict, sep=\"\\n\\t...\")\n", " return type(clsname, supers, clsdict)\n", "\n", "\n", "class A:\n", " pass\n", "\n", "\n", "print(\"---Create class B---\")\n", "\n", "\n", "class B(A, metaclass=MetaFunc):\n", " data = 1\n", "\n", " def __init__(self):\n", " print(\"Create instance of B\")\n", "\n", " def meth(self, arg):\n", " return self.data + arg\n", "\n", "\n", "print(\"---Create instance b---\")\n", "b = B()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Inheritance an Instance\n", "- Metaclass Declrations are inherited by subclasses\n", "- Metaclass attributes are not inherited by class instance\n", "- Metaclass attribute are aquired by classes" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Meta: Super\n", "Meta: Sub\n" ] } ], "source": [ "# Definition\n", "\n", "\n", "class Meta(type):\n", " def __new__(meta, clsname, supers, clsdict):\n", " print(\"Meta: \", clsname)\n", " return type.__new__(meta, clsname, supers, clsdict)\n", "\n", " def toast(self):\n", " return \"toast\"\n", "\n", "\n", "class Super(metaclass=Meta):\n", " def spam(self):\n", " return \"spam\"\n", "\n", "\n", "class Sub(Super):\n", " def eggs(self):\n", " return \"eggs\"" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "ename": "AttributeError", "evalue": "'Sub' object has no attribute 'toast'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meggs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mspam\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtoast\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'Sub' object has no attribute 'toast'" ] } ], "source": [ "# Instance\n", "\n", "x = Sub()\n", "x.eggs()\n", "x.spam()\n", "x.toast()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "'toast'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Class\n", "x = Sub()\n", "Sub.eggs(x)\n", "Sub.spam(x)\n", "Sub.toast()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Metaclass Methods VS Class Method\n", "- Much the same, but\n", " - Metaclasses methods are not accessible excpet through the class\n", " - Metaclasses do not need **classmethod** declarator" ] } ], "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.5.1" } }, "nbformat": 4, "nbformat_minor": 0 }