{ "metadata": { "name": "bar_foo" }, "nbformat": 2, "worksheets": [ { "cells": [ { "cell_type": "markdown", "source": [ "## What does `bar.foo` do?", "", "An exploration of Python assignment, objects, attributes and descriptors", "", "2014-03-27", "", "Ottawa Python Authors Group" ] }, { "cell_type": "markdown", "source": [ "## Overview", "", "1. Names and values", "2. Attribute access", "3. Selectively run code", "4. Complete control", "5. Terrible ideas" ] }, { "cell_type": "markdown", "source": [ "## 1. Names and values", "", "What does `foo` do?", "", "Better question: how can we make `foo` do something we want?" ] }, { "cell_type": "markdown", "source": [ "## 1.1. Simple assignment" ] }, { "cell_type": "code", "collapsed": false, "input": [ "foo" ], "language": "python", "outputs": [ { "ename": "NameError", "evalue": "name 'foo' is not defined", "output_type": "pyerr", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfoo\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'foo' is not defined" ] } ], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "foo = 'tesla'", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 136, "text": [ "'tesla'" ] } ], "prompt_number": 136 }, { "cell_type": "code", "collapsed": false, "input": [ "del foo", "foo" ], "language": "python", "outputs": [ { "ename": "NameError", "evalue": "name 'foo' is not defined", "output_type": "pyerr", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdel\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mfoo\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'foo' is not defined" ] } ], "prompt_number": 137 }, { "cell_type": "markdown", "source": [ "## 1.2. `def`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def foo():", " return 42", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 138, "text": [ "" ] } ], "prompt_number": 138 }, { "cell_type": "code", "collapsed": false, "input": [ "foo2 = foo", "foo2()" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 139, "text": [ "42" ] } ], "prompt_number": 139 }, { "cell_type": "code", "collapsed": false, "input": [ "foo2 is foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 10, "text": [ "True" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "source": [ "## 1.3. `class`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Foo(object):", " pass", "", "Foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 140, "text": [ "__main__.Foo" ] } ], "prompt_number": 140 }, { "cell_type": "markdown", "source": [ "## 1.4. `for..in`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "for foo in [1, 2, 3]:", " pass", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 141, "text": [ "3" ] } ], "prompt_number": 141 }, { "cell_type": "markdown", "source": [ "## 1.5. `import`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import sys as foo", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 142, "text": [ "" ] } ], "prompt_number": 142 }, { "cell_type": "markdown", "source": [ "## 1.6. `try..except`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "try:", " 1/0", "except ZeroDivisionError as foo:", " pass", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 143, "text": [ "ZeroDivisionError('integer division or modulo by zero')" ] } ], "prompt_number": 143 }, { "cell_type": "markdown", "source": [ "## 1.7. `with`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "with open('/tmp/things', 'w') as foo:", " pass", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 144, "text": [ "" ] } ], "prompt_number": 144 }, { "cell_type": "markdown", "source": [ "## 1.8. Decorators" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def serious_decorator(f):", " return str(f) + ' is much too silly'", "", "@serious_decorator", "def foo():", " return 42", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 145, "text": [ "' is much too silly'" ] } ], "prompt_number": 145 }, { "cell_type": "markdown", "source": [ "## 1.9. Evil ways to bind names" ] }, { "cell_type": "markdown", "source": [ "## 1.9.1. Evil use of `vars`, `locals`, `globals`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "vars()['foo'] = 'tesla'", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 146, "text": [ "'tesla'" ] } ], "prompt_number": 146 }, { "cell_type": "markdown", "source": [ "## 1.9.2. Evil use of list comprehensions (does not work in Python 3)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "[None for foo in [1,2,3]]" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 25, "text": [ "[None, None, None]" ] } ], "prompt_number": 25 }, { "cell_type": "code", "collapsed": false, "input": [ "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 26, "text": [ "3" ] } ], "prompt_number": 26 }, { "cell_type": "markdown", "source": [ "## 1.9.3. Evil stack frame manipulation" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def im_in_ur_frame():", " import sys", " frame = sys._getframe(1)", " frame.f_locals['foo'] = 'writin ur vars'", " ", "im_in_ur_frame()", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 148, "text": [ "'writin ur vars'" ] } ], "prompt_number": 148 }, { "cell_type": "markdown", "source": [ "## 1.9.4. Evil use of `import`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from os.path import *", "", "ismount" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 149, "text": [ "" ] } ], "prompt_number": 149 }, { "cell_type": "markdown", "source": [ "## 1.10. Name binding recap", "", "1. assignment with `=`", "2. `def`", "3. `class`", "4. `for..in`", "5. `import`", "6. `try..except`", "7. `with`", "8. function/class decorators", "9. evil" ] }, { "cell_type": "markdown", "source": [ "## 2. Attribute access", "", "How can we make `bar.foo` do something we want?" ] }, { "cell_type": "markdown", "source": [ "## 2.1. Builtin objects with attributes" ] }, { "cell_type": "code", "collapsed": false, "input": [ "bar = 2 + 3j", "", "bar.imag" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 150, "text": [ "3.0" ] } ], "prompt_number": 150 }, { "cell_type": "code", "collapsed": false, "input": [ "bar = open(\"/tmp/things\", \"w\")", "", "bar.closed" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 151, "text": [ "False" ] } ], "prompt_number": 151 }, { "cell_type": "code", "collapsed": true, "input": [ "bar.close()" ], "language": "python", "outputs": [], "prompt_number": 152 }, { "cell_type": "markdown", "source": [ "## 2.2. `collections.namedtuple`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from collections import namedtuple", "Hotel = namedtuple('Hotel', 'foo baz')", "", "bar = Hotel(42, 'shoe')", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 153, "text": [ "42" ] } ], "prompt_number": 153 }, { "cell_type": "code", "collapsed": false, "input": [ "bar" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 40, "text": [ "Hotel(foo=42, baz='shoe')" ] } ], "prompt_number": 40 }, { "cell_type": "code", "collapsed": false, "input": [ "bar[1]" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 41, "text": [ "'shoe'" ] } ], "prompt_number": 41 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 'tesla'" ], "language": "python", "outputs": [ { "ename": "AttributeError", "evalue": "can't set attribute", "output_type": "pyerr", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mbar\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfoo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'tesla'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: can't set attribute" ] } ], "prompt_number": 42 }, { "cell_type": "markdown", "source": [ "## 2.3. Custom object attributes", "", "\"Normal\" Python objects" ] }, { "cell_type": "markdown", "source": [ "## 2.3.1. Assigning object attributes (slightly unusual)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Hotel(object):", " pass", "", "bar = Hotel()", "", "bar" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 154, "text": [ "<__main__.Hotel at 0x2d8d5d0>" ] } ], "prompt_number": 154 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 'tesla'", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 155, "text": [ "'tesla'" ] } ], "prompt_number": 155 }, { "cell_type": "markdown", "source": [ "## 2.3.2. Assign attributes within a method" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Motel(object):", " def __init__(self):", " self.foo = 'vespa'", "", "bar = Motel()", "", "bar" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 156, "text": [ "<__main__.Motel at 0x2d9e2d0>" ] } ], "prompt_number": 156 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 157, "text": [ "'vespa'" ] } ], "prompt_number": 157 }, { "cell_type": "markdown", "source": [ "## 2.3.3. Evil attribute assignment with `setattr`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Martini(object):", " pass", "", "bar = Martini()", "", "setattr(bar, 'foo', 'twenty bucks')", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 158, "text": [ "'twenty bucks'" ] } ], "prompt_number": 158 }, { "cell_type": "markdown", "source": [ "## 2.3.4. Evil attribute assignment via `__dict__`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Hangout(object):", " pass", "", "bar = Hangout()", "", "bar.__dict__['foo'] = 'hipster'", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 159, "text": [ "'hipster'" ] } ], "prompt_number": 159 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.__dict__" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 160, "text": [ "{'foo': 'hipster'}" ] } ], "prompt_number": 160 }, { "cell_type": "markdown", "source": [ "EXTRA WARNING: `__dict__` is an implementation detail and isn't guaranteed to be there." ] }, { "cell_type": "markdown", "source": [ "## 2.4. Class attributes", "", "Python classes are objects too" ] }, { "cell_type": "markdown", "source": [ "## 2.4.1. Assigning class attributes (monkey patch)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bar(object):", " pass", "", "Bar" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 162, "text": [ "__main__.Bar" ] } ], "prompt_number": 162 }, { "cell_type": "code", "collapsed": false, "input": [ "Bar.foo = 'Blanche de Chambly'", "", "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 163, "text": [ "'Blanche de Chambly'" ] } ], "prompt_number": 163 }, { "cell_type": "markdown", "source": [ "## 2.4.2. Assign within the class definition" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bar(object):", " foo = 'Porter Baltique'", " ", "Bar" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 164, "text": [ "__main__.Bar" ] } ], "prompt_number": 164 }, { "cell_type": "code", "collapsed": false, "input": [ "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 165, "text": [ "'Porter Baltique'" ] } ], "prompt_number": 165 }, { "cell_type": "markdown", "source": [ "## 2.4.3. Object attributes > class attributes" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Club(object):", " pass", "", "bar = Club()", "", "bar.foo = 'techno'", "Club.foo = 'chocololate'", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 166, "text": [ "'techno'" ] } ], "prompt_number": 166 }, { "cell_type": "code", "collapsed": false, "input": [ "del bar.foo", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 167, "text": [ "'chocololate'" ] } ], "prompt_number": 167 }, { "cell_type": "markdown", "source": [ "## 2.4.4. Evil class assignment with `setattr`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Dive(object):", " pass", "", "setattr(Dive, 'foo', 'rusty nail')", "", "Dive.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 168, "text": [ "'rusty nail'" ] } ], "prompt_number": 168 }, { "cell_type": "markdown", "source": [ "## 2.4.5. Assignment of class attributes, the wacky ways" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bestiary(object):", " import sys as mod", " class Klass(object):", " pass", " for looper in [1, 2, 3]:", " pass", " try:", " 1/0", " except ZeroDivisionError as exc:", " pass", " with open('/tmp/wat', 'w') as closedfile:", " pass", "", "Bestiary.mod" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 169, "text": [ "" ] } ], "prompt_number": 169 }, { "cell_type": "code", "collapsed": false, "input": [ "Bestiary.Klass" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 170, "text": [ "__main__.Klass" ] } ], "prompt_number": 170 }, { "cell_type": "code", "collapsed": false, "input": [ "Bestiary.looper" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 171, "text": [ "3" ] } ], "prompt_number": 171 }, { "cell_type": "code", "collapsed": false, "input": [ "Bestiary.exc" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 172, "text": [ "ZeroDivisionError('integer division or modulo by zero')" ] } ], "prompt_number": 172 }, { "cell_type": "code", "collapsed": false, "input": [ "Bestiary.closedfile" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 173, "text": [ "" ] } ], "prompt_number": 173 }, { "cell_type": "markdown", "source": [ "## 2.5. Catch-all `__getattr__` method" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Lounge(object):", " def __getattr__(self, name):", " if name == 'foo':", " return 'lizard'", " raise AttributeError(\"%r object has no attribute %r\"", " % (self.__class__.__name__, name))", " ", "bar = Lounge()", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 185, "text": [ "'lizard'" ] } ], "prompt_number": 185 }, { "cell_type": "code", "collapsed": false, "input": [ "hasattr(bar, 'baz')" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 186, "text": [ "False" ] } ], "prompt_number": 186 }, { "cell_type": "markdown", "source": [ "## 2.5.1. Object attribute > Class attribute > `__getattr__`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "Lounge.foo = 'larry'", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 179, "text": [ "'larry'" ] } ], "prompt_number": 179 }, { "cell_type": "code", "collapsed": false, "input": [ "del Lounge.foo", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 180, "text": [ "'lizard'" ] } ], "prompt_number": 180 }, { "cell_type": "markdown", "source": [ "## 2.6. MRO: Method (attribute) resolution order" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Structure(object):", " pass", "class Dancing(Structure):", " pass", "class Drinking(Structure):", " pass", "class Bar(Dancing, Drinking):", " pass", "", "Bar.__mro__" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 183, "text": [ "(__main__.Bar, __main__.Dancing, __main__.Drinking, __main__.Structure, object)" ] } ], "prompt_number": 183 }, { "cell_type": "code", "collapsed": false, "input": [ "Structure.foo = 'bricks'", "Drinking.foo = 'drinks'", "", "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 184, "text": [ "'drinks'" ] } ], "prompt_number": 184 }, { "cell_type": "markdown", "source": [ "## 2.7. Order so far", "", "1. object attributes", "2. class attributes and non-data descriptors in type's MRO", "3. `__getattr__` in type's MRO" ] }, { "cell_type": "markdown", "source": [ "## 3. Run code in response to attribute access", "", "With more precision than a `__getattr__` method" ] }, { "cell_type": "markdown", "source": [ "## 3.1. Methods", "", "What about Python methods? There is something going on here:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bar(object):", " def foo(self):", " pass", "", "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 187, "text": [ "" ] } ], "prompt_number": 187 }, { "cell_type": "code", "collapsed": false, "input": [ "bar = Bar()", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 188, "text": [ ">" ] } ], "prompt_number": 188 }, { "cell_type": "markdown", "source": [ "## 3.1.1. Methods, a closer look (very unusual code)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bar(object):", " pass", "", "def foo(thing):", " pass", "", "Bar.foo = foo", "", "foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 197, "text": [ "" ] } ], "prompt_number": 197 }, { "cell_type": "code", "collapsed": false, "input": [ "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 198, "text": [ "" ] } ], "prompt_number": 198 }, { "cell_type": "code", "collapsed": false, "input": [ "Bar().foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 199, "text": [ ">" ] } ], "prompt_number": 199 }, { "cell_type": "code", "collapsed": false, "input": [ "foo is Bar.__dict__['foo']" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 200, "text": [ "True" ] } ], "prompt_number": 200 }, { "cell_type": "markdown", "source": [ "## 3.2. Non-data descriptors (won't be on the test)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class AboutFoo(object):", " def __get__(self, instance, owner):", " return instance, owner", "", "class Bar(object):", " foo = AboutFoo()", "", "Bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 210, "text": [ "(None, __main__.Bar)" ] } ], "prompt_number": 210 }, { "cell_type": "code", "collapsed": false, "input": [ "bar = Bar()", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 211, "text": [ "(<__main__.Bar at 0x2e05110>, __main__.Bar)" ] } ], "prompt_number": 211 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 42", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 212, "text": [ "42" ] } ], "prompt_number": 212 }, { "cell_type": "markdown", "source": [ "Methods, `staticmethod` and `classmethod` are implemented as non-data descriptors." ] }, { "cell_type": "markdown", "source": [ "## 3.3. `property`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class ReadOnly(object):", " def _get_foo(self):", " print(\"accessed foo!\")", " return 42", "", " foo = property(_get_foo)", "", "ReadOnly.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 251, "text": [ "" ] } ], "prompt_number": 251 }, { "cell_type": "code", "collapsed": false, "input": [ "bar = ReadOnly()", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "accessed foo!" ] }, { "output_type": "pyout", "prompt_number": 252, "text": [ "42" ] } ], "prompt_number": 252 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 9" ], "language": "python", "outputs": [ { "ename": "AttributeError", "evalue": "can't set attribute", "output_type": "pyerr", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mbar\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfoo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m9\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: can't set attribute" ] } ], "prompt_number": 253 }, { "cell_type": "markdown", "source": [ "## 3.3.1. Setter" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class ReadWrite(object):", " def _get_foo(self):", " return self._foo", "", " def _set_foo(self, value):", " print(\"just set foo to %r!\" % value)", " self._foo = value", " ", " foo = property(_get_foo, _set_foo)", "", "bar = ReadWrite()", "bar.foo" ], "language": "python", "outputs": [ { "ename": "AttributeError", "evalue": "'ReadWrite' object has no attribute '_foo'", "output_type": "pyerr", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mbar\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mReadWrite\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[0mbar\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfoo\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m/home/ian/git/bar_foo/\u001b[0m in \u001b[0;36m_get_foo\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mReadWrite\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobject\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_get_foo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\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[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_foo\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;32mdef\u001b[0m \u001b[0m_set_foo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mAttributeError\u001b[0m: 'ReadWrite' object has no attribute '_foo'" ] } ], "prompt_number": 257 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 9" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "just set foo to 9!" ] } ], "prompt_number": 258 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 259, "text": [ "9" ] } ], "prompt_number": 259 }, { "cell_type": "markdown", "source": [ "## 3.3.2. Prettier?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Propertify(object):", " @property", " def foo(self):", " return self._foo + 1", " ", " @foo.setter", " def foo(self, value):", " self._foo = value", "", "bar = Propertify()", "bar.foo = 2", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 268, "text": [ "3" ] } ], "prompt_number": 268 }, { "cell_type": "markdown", "source": [ "## 3.3.2. Overriding `del` (for completeness, I guess)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Oddball(object):", " excuses = [\"No\", \"I refuse\", \"Go away\"]", " ", " def _del_foo(self):", " print(self.excuses.pop(0))", " ", " foo = property(fdel=_del_foo)", "", "bar = Oddball()", "del bar.foo" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "No" ] } ], "prompt_number": 284 }, { "cell_type": "code", "collapsed": false, "input": [ "del bar.foo" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "I refuse" ] } ], "prompt_number": 285 }, { "cell_type": "code", "collapsed": false, "input": [ "del bar.foo" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Go away" ] } ], "prompt_number": 286 }, { "cell_type": "markdown", "source": [ "## 3.4. Data descriptors (also not on the test)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class ThisIsMyFoo(object):", " def __get__(self, instance, owner):", " return instance, owner", " def __set__(self, instance, value):", " print(\"tried to set %r value to %r\" % (instance, value))", "", "class Bar(object):", " foo = ThisIsMyFoo()", "", "bar = Bar()", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 205, "text": [ "(<__main__.Bar at 0x2d9efd0>, __main__.Bar)" ] } ], "prompt_number": 205 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 42" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "tried to set <__main__.Bar object at 0x2d9efd0> value to 42" ] } ], "prompt_number": 206 }, { "cell_type": "markdown", "source": [ "`property` is implemented as a data descriptor." ] }, { "cell_type": "markdown", "source": [ "## 3.5. Order recap", "", "1. data descriptors in type's MRO", "2. object attributes", "3. class attributes and non-data descriptors in type's MRO", "4. `__getattr__` in type's MRO" ] }, { "cell_type": "markdown", "source": [ "## 4. Complete attribute access control" ] }, { "cell_type": "markdown", "source": [ "## 4.1. Preemptive `__getattribute__` method", "", "(yes, confusingly similar to `__getattr__`)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Casino(object):", " def __getattribute__(self, name):", " if name == 'foo':", " return 'rat'", " return object.__getattribute__(self, name)", " ", "bar = Casino()", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 213, "text": [ "'rat'" ] } ], "prompt_number": 213 }, { "cell_type": "code", "collapsed": false, "input": [ "bar.foo = 'mouse'", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 214, "text": [ "'rat'" ] } ], "prompt_number": 214 }, { "cell_type": "markdown", "source": [ "`object.__getattribute__` (and `type._getattribute__`) define all Python's special descriptor behaviour.", "", "Overriding this method lets us control or override almost everything about attribute access for objects of this type." ] }, { "cell_type": "markdown", "source": [ "## 4.2. PyPy's transparent proxy" ] }, { "cell_type": "markdown", "source": [ "Catch and modify everything access except `type(bar)`. You may choose type returned when creating the proxy." ] }, { "cell_type": "code", "collapsed": true, "input": [ "from tputil import make_proxy", "", "history = []", "def recorder(operation):", " history.append(operation)", " return operation.delegate()", "", "o = make_proxy(recorder, obj=[])" ], "language": "python", "outputs": [] }, { "cell_type": "markdown", "source": [ "## 4.3. Final order", "", "1. PyPy transparent proxy", "2. `__getattribute__` in type's MRO", "3. data descriptors in type's MRO", "4. object attributes", "5. class attributes and non-data descriptors in type's MRO", "6. `__getattr__` in type's MRO" ] }, { "cell_type": "markdown", "source": [ "## 4.4. All the `foo`" ] }, { "cell_type": "code", "collapsed": true, "input": [ "class Parent(object):", " def __getattribute__(self, name):", " if name == 'foo': return 'Second'", " return object.__getattribute__(self, name)", " foo = property(lambda self:'Fourth')", " def __getattr__(self, name):", " if name == 'foo': return 'Ninth'", " raise AttributeError(\"%r object has no attribute %r\"", " % (self.__class__.__name__, name))", "", "class Child(Parent):", " def __getattribute__(self, name):", " if name == 'foo': return 'First'", " return object.__getattribute__(self, name)", " foo = property(lambda self:'Third')", " def __getattr__(self, name):", " if name == 'foo': return 'Eighth'", " raise AttributeError(\"%r object has no attribute %r\"", " % (self.__class__.__name__, name))" ], "language": "python", "outputs": [], "prompt_number": 215 }, { "cell_type": "code", "collapsed": false, "input": [ "bar = Child()", "bar.__dict__['foo'] = 'Fifth'", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 216, "text": [ "'First'" ] } ], "prompt_number": 216 }, { "cell_type": "code", "collapsed": false, "input": [ "del Child.__getattribute__", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 217, "text": [ "'Second'" ] } ], "prompt_number": 217 }, { "cell_type": "code", "collapsed": false, "input": [ "del Parent.__getattribute__", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 218, "text": [ "'Third'" ] } ], "prompt_number": 218 }, { "cell_type": "code", "collapsed": false, "input": [ "del Child.foo", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 219, "text": [ "'Fourth'" ] } ], "prompt_number": 219 }, { "cell_type": "code", "collapsed": false, "input": [ "del Parent.foo", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 220, "text": [ "'Fifth'" ] } ], "prompt_number": 220 }, { "cell_type": "code", "collapsed": false, "input": [ "Child.foo = 'Sixth'", "Parent.foo = 'Seventh'", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 221, "text": [ "'Fifth'" ] } ], "prompt_number": 221 }, { "cell_type": "code", "collapsed": false, "input": [ "del bar.foo", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 222, "text": [ "'Sixth'" ] } ], "prompt_number": 222 }, { "cell_type": "code", "collapsed": false, "input": [ "del Child.foo", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 223, "text": [ "'Seventh'" ] } ], "prompt_number": 223 }, { "cell_type": "code", "collapsed": false, "input": [ "del Parent.foo", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 224, "text": [ "'Eighth'" ] } ], "prompt_number": 224 }, { "cell_type": "code", "collapsed": false, "input": [ "del Child.__getattr__", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 225, "text": [ "'Ninth'" ] } ], "prompt_number": 225 }, { "cell_type": "markdown", "source": [ "## 5. Terrible ideas", "", "Seriously?" ] }, { "cell_type": "markdown", "source": [ "## 5.1. Meta-Evil" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class YouGetAFooEveryoneGetsAFoo(type):", " def __call__(cls):", " obj = type.__call__(cls)", " obj.foo = 'under your seat'", " return obj", " ", "class Bar(object):", " __metaclass__ = YouGetAFooEveryoneGetsAFoo", "", "Bar().foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 226, "text": [ "'under your seat'" ] } ], "prompt_number": 226 }, { "cell_type": "markdown", "source": [ "## 5.2. `gc` Evil" ] }, { "cell_type": "code", "collapsed": false, "input": [ "target = ('not a string',)", "", "class Thing(object):", " pass", "", "bar = Thing()", "baz = Thing()", "bar.foo = target", "baz.foo = target", "", "import gc", "for d in gc.get_referrers(target):", " for k, v in d.items():", " if v == target:", " d[k] = 86", "", "bar.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 234, "text": [ "86" ] } ], "prompt_number": 234 }, { "cell_type": "code", "collapsed": false, "input": [ "baz.foo" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 235, "text": [ "86" ] } ], "prompt_number": 235 }, { "cell_type": "code", "collapsed": false, "input": [ "target" ], "language": "python", "outputs": [ { "output_type": "pyout", "prompt_number": 236, "text": [ "86" ] } ], "prompt_number": 236 }, { "cell_type": "markdown", "source": [ "## 5.3. Bytecode Evil" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Bar(object):", " foo = 'steak'", " baz = 'pogo'", "", "def i_think_you_meant_baz(f):", " import marshal", " code = marshal.dumps(f.func_code)", " f.func_code = marshal.loads(code.replace('foo', 'baz'))", " return f", "", "@i_think_you_meant_baz", "def dinner():", " print(Bar.foo)", "", "dinner()" ], "language": "python", "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "pogo" ] } ], "prompt_number": 228 }, { "cell_type": "markdown", "source": [ "## 6. Coping", "", "1. Don't over-use classes (`namedtuple` is quite nice!)", "2. When you use classes, don't use inheritance", "3. When you use inheritance, don't nest deeply", "4. Don't use `__getattr__` unless you really need it", "5. Don't use `__getattribute__`, you probably don't need it", "6. Use metaclasses only when the result is awesome", "7. Use `gc` only for debugging", "8. Never mess with bytecode" ] }, { "cell_type": "markdown", "source": [ "## Thank you!", "", "Questions?" ] } ] } ] }