{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Attribute Access\n", "- property \n", "- descriptor\n", "- **`__getattr__`**, **`__setattr__`**\n", "- **`__getattribute__`**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Properties\n", "- Route a specific attributes's get, set and delete operations to functions provided\n", "- Created with **property** built-in\n", "- They are inherited by subclasses and instances\n", "\n", "## Basics\n", "```python\n", "attribute = property(fget, fset, fdel, doc)\n", "```\n", "\n", "- None of these function are required\n", "- The default of them are **None**\n", " - which means this operation is not supported\n", " - attempting it raises an **AttributeError**\n", " \n", " \n", "- **property** call returns a property object, which we assign to the name of the attribute to be managed in the class scope\n", "- require new-style **object** derivation" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Fetch...\n", "Change...\n", "name property docs\n", "Remove...\n" ] } ], "source": [ "class Person:\n", " def __init__(self, name):\n", " self._name = name\n", "\n", " def get_name(self):\n", " print(\"Fetch...\")\n", " return self._name\n", "\n", " def set_name(self, value):\n", " print(\"Change...\")\n", " self._name = value\n", "\n", " def del_name(self):\n", " print(\"Remove...\")\n", " del self._name\n", "\n", " name = property(get_name, set_name, del_name, \"name property docs\")\n", "\n", "\n", "bob = Person(\"Bob Smith\")\n", "bob.name\n", "bob.name = \"Robert Smith\"\n", "print(Person.name.__doc__)\n", "del bob.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### properties with decorators" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Fetch...\n", "Change...\n", "name property docs\n", "Remove...\n" ] } ], "source": [ "class Person:\n", " def __init__(self, name):\n", " self._name = name\n", "\n", " @property\n", " def name(self):\n", " \"name property docs\"\n", " print(\"Fetch...\")\n", " return self._name\n", "\n", " @name.setter\n", " def name(self, value):\n", " print(\"Change...\")\n", " self._name = value\n", "\n", " @name.deleter\n", " def name(self):\n", " print(\"Remove...\")\n", " del self._name\n", "\n", "\n", "bob = Person(\"Bob Smith\")\n", "bob.name\n", "bob.name = \"Robert Smith\"\n", "print(Person.name.__doc__)\n", "del bob.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Descrptors\n", "- Descriptors are created as independent classes and are assigned to class attributes just like method functions\n", "- Like a property, a descriptor manages a single, specific attribute\n", "- A property is a simplified way to create a specific type of descriptor that runs method functions on attribute access\n", " - Descriptor is a more general tool\n", " - state information\n", " - inheritance\n", " - composition\n", "- Works only for new-style class (Both descriptor and its client class)\n", "\n", "## Basics\n", "```python\n", "class Descriptor:\n", " 'docstring'\n", " def __get__(self, instance, owner):\n", " \"\"\"\n", " owner: specify the class to which descriptor is attached\n", " instance: the instance which the attribute was accessed (for instnce.attr) or Nonr (for class.attr)\n", " \"\"\"\n", " ...\n", " return attr\n", " \n", " def __set__(self, instance, value):\n", " ...\n", " \n", " def __delete__(self, instance):\n", " ...\n", "```\n", "\n", "Unlike property, omitting a **`__set__`** allows the descriptor attribute's name to be assigned" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "get\n" ] }, { "ename": "AttributeError", "evalue": "cannot set", "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 14\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[0;32m 15\u001b[0m \u001b[0mc\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 16\u001b[1;33m \u001b[0mc\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0ma\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36m__set__\u001b[1;34m(*args)\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__set__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 8\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'cannot set'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 9\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[1;32mclass\u001b[0m \u001b[0mC\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mAttributeError\u001b[0m: cannot set" ] } ], "source": [ "# Really Read-Only\n", "\n", "\n", "class Descriptor:\n", " def __get__(*args):\n", " print(\"get\")\n", "\n", " def __set__(*args):\n", " raise AttributeError(\"cannot set\")\n", "\n", "\n", "class C:\n", " a = Descriptor()\n", "\n", "\n", "c = C()\n", "c.a\n", "c.a = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Assign an instance of the descriptor class to a class attribute to enable it to be inherited by all instances of the class" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Name:\n", " \"name descriptor docs\"\n", "\n", " def __get__(self, instance, owner):\n", " print(\"Fetch...\")\n", " return instance._name\n", "\n", " def __set__(self, instance, value):\n", " print(\"Change...\")\n", " instance._name = value\n", "\n", " def __delete__(self, instance):\n", " print(\"Remove...\")\n", " del instance._name\n", "\n", "\n", "class Person:\n", " def __init__(self, name):\n", " self._name = name\n", "\n", " name = Name() # It must be assign like this instead of a self inatnce attribute\n", "\n", "\n", "bob = Person(\"Bob Smith\")\n", "bob.name\n", "bob.name = \"Robert Smith\"\n", "del bob.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- If the descriptor class is not userful outside the client class, it's perfectly reasonable to embed it inside its client as subclass\n", " - By doing so it won't clash with any names outside the class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using State Information in Descriptors\n", "### Descriptor state\n", "- Data internal to the workings of the descriptor\n", "- Data spans all instnaces\n", "- Can vary per attribute apperance\n", "\n", "### Instance state\n", "- Date that created by the client class\n", "- Can vary per client class instance" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Des Set\n", "Ins Set\n", "Des Get\n", "Ins Get\n", "10 2\n", "------------------------------\n", "Des Set\n", "Ins Set\n", "Des Get\n", "Ins Get\n", "30 4\n", "------------------------------\n", "Des Get\n", "Ins Get\n", "30 2\n" ] } ], "source": [ "class DescState:\n", " def __get__(self, instance, owner):\n", " print(\"Des Get\")\n", " return self._des * 10\n", "\n", " def __set__(self, instance, value):\n", " print(\"Des Set\")\n", " self._des = value\n", "\n", "\n", "class C:\n", " def __init__(self):\n", " self._des = 0\n", " self._ins = 0\n", "\n", " des = DescState()\n", "\n", " class InstState:\n", " def __get__(self, instance, owner):\n", " print(\"Ins Get\")\n", " return instance._ins\n", "\n", " def __set__(self, instance, value):\n", " print(\"Ins Set\")\n", " instance._ins = value\n", "\n", " ins = InstState()\n", "\n", "\n", "c1 = C()\n", "c1.des = 1\n", "c1.ins = 2\n", "print(c1.des, c1.ins)\n", "\n", "print(\"-\" * 30)\n", "\n", "c2 = C()\n", "c2.des = 3\n", "c2.ins = 4\n", "print(c2.des, c2.ins)\n", "\n", "print(\"-\" * 30)\n", "\n", "print(c1.des, c1.ins)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# `__getattr__` and `__getattribute__`\n", "Well suited to general delegation-based coding patterns\n", "\n", "## Methods Overview\n", "- **`__getattr__`**\n", " - For undefined attributes\n", "\n", "- **`__getattribute__`**\n", " - For every attribute\n", " - Need to avoid recursive loops (Can be done by routing to superclass)\n", " - Only available for new-style classes\n", "\n", "- **`__setattr__`**\n", " - For every attribute fetch \n", " - Need to avoid recursive loops\n", "\n", "- **`__delattr__`**\n", "\n", "```python\n", "class C:\n", " def __getattr__(self, name):\n", " '''\n", " name: the string name of the attribute being accessed\n", " '''\n", " ...\n", " return attr\n", " \n", " def __getattribute__(self, name):\n", " ...\n", " return attr\n", " \n", " def __setattr__(self, name, value):\n", " '''\n", " value: the object being assigned to the attributeb\n", " '''\n", " \n", " def __delattr__(self, name):\n", "```\n", "\n", "## Avoiding Loops\n", "\n", "- The following code will trigger infinite loop\n", "```python\n", "class C:\n", " def __getattribute__(self, name):\n", " x = self.other\n", " return x\n", "```\n", "\n", "- Routing to superclass (object)\n", "```python\n", "class C:\n", " def __getattribute(self, name):\n", " x = object.__getattribute__(self, 'other')\n", " return x\n", "```\n", "\n", "## Intercept Built-in Operation Attributes\n", "**`__getattr__`** and **`__getattribute__`** are ideal for delegation except ***method-name attributes implicityly fetched by bulit-in operation*** \n", "These methods may even not be run at all \n", " \n", "- In Python2, **`__getattr__`** can run for such attributes while it cannot in Python3\n", "- **`__getattribute__`** cannot run for such attributes in both versions" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('getattr: ', '__getitem__')\r\n" ] } ], "source": [ "%%python2\n", "class GetAttr:\n", " eggs = 88\n", " def __init__(self):\n", " self.spam = 77\n", " \n", " def __getattr__(self, attr):\n", " print('getattr: ', attr)\n", " if attr == '__str__':\n", " return lambda *args: '[Getattr Str]'\n", " else:\n", " return lambda *args: None\n", " \n", "g = GetAttr()\n", "g[0]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "ename": "TypeError", "evalue": "'GetAttr' object does not support indexing", "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 12\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[0mg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mGetAttr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 14\u001b[1;33m \u001b[0mg\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[0m", "\u001b[1;31mTypeError\u001b[0m: 'GetAttr' object does not support indexing" ] } ], "source": [ "class GetAttr:\n", " eggs = 88\n", "\n", " def __init__(self):\n", " self.spam = 77\n", "\n", " def __getattr__(self, attr):\n", " print(\"getattr: \", attr)\n", " if attr == \"__str__\":\n", " return lambda *args: \"[Getattr Str]\"\n", " else:\n", " return lambda *args: None\n", "\n", "\n", "g = GetAttr()\n", "g[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Operator overloading methods implicityly run by built-in operations are never routed through either attribute interception method in Python3\n", "- If wrapped classes may contain operator overloading methods, those methods must be redefined in the wrapper class in order to delegate to the wrapped object" ] } ], "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.4.4" } }, "nbformat": 4, "nbformat_minor": 0 }