Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Jawabiscuit/b59df0d2097de0731dd5 to your computer and use it in GitHub Desktop.
Save Jawabiscuit/b59df0d2097de0731dd5 to your computer and use it in GitHub Desktop.
PyCon 2014 discussion by Mike Müller
{
"metadata": {
"name": "",
"signature": "sha256:6589f7bd75bdbe1836ad76975165694574849089fc174c676910b9778751f757"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#Descriptors and Metaclasses - Understanding and Using Python's More Advanced Features\n",
"\n",
"A [PyCon 2014](http://pyvideo.org/category/50/pycon-us-2014) discussion by [Mike M\u00fcller](http://pyvideo.org/speaker/332/mike-muller)\n",
"\n",
"##Summary\n",
"\n",
"Descriptors and metaclasses are advanced Python features. While it is possible to write Python programs without active of knowledge of them, knowing how they work provides a deeper understanding about the language. Using examples, you will learn how they work and when to use as well as when better not to use them. Use cases provide working code that can serve as a base for own solutions."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"###Variations of Descriptors\n",
"\n",
"Simple property"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class Square(object):\n",
" def __init__(self, side):\n",
" self.side = side\n",
" def aget(self):\n",
" return self.side * self.side\n",
" def aset(self, value):\n",
" print(\"Can't change the area.\")\n",
" def adel(self):\n",
" print(\"Can't delete the area.\")\n",
" area = property(aget, aset, adel, doc=\"Area of the square\")"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s = Square(5)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 4
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.area"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 5,
"text": [
"25"
]
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.area = 36"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Can't change the area.\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"del s.area"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Can't delete the area.\n"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Decorators"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class Square(object):\n",
" def __init__(self, side):\n",
" self.side = side\n",
" \n",
" @property\n",
" def area(self):\n",
" '''Calculate the area of the square when the\n",
" attribute is accessed.'''\n",
" return self.side * self.side\n",
" \n",
" @area.setter\n",
" def area(self, value):\n",
" '''Don't allow setting.'''\n",
" print \"Can't set the area.\"\n",
" \n",
" @area.deleter\n",
" def area(self):\n",
" '''Don't allow deleting.'''\n",
" print \"Can't delete the area.\"\n",
"\n",
"if __name__ == \"__main__\":\n",
" s = Square(5)\n",
" print('Square: %s' % s)\n",
" print('Area: %s' % s.area)\n",
" s.area = 36\n",
" del s.area\n"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Square: <__main__.Square object at 0x04D79450>\n",
"Area: 25\n",
"Can't set the area.\n",
"Can't delete the area.\n"
]
}
],
"prompt_number": 13
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nested Property"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def nested_property(func):\n",
" '''Makes defining properties easier to read.'''\n",
" names = func()\n",
" names['doc'] = func.__doc__\n",
" return property(**names)\n",
"\n",
"\n",
"class Square(object):\n",
" def __init__(self, side):\n",
" self.side = side\n",
" \n",
" @nested_property\n",
" def area():\n",
" '''property of Square defined using nested methods'''\n",
" \n",
" def fget(self):\n",
" return self.side * self.side\n",
" \n",
" def fset(self, value):\n",
" print(\"Can't change the area.\")\n",
" \n",
" def fdel(self):\n",
" print(\"Can't delete the area.\")\n",
" \n",
" print locals()\n",
" return locals()\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" s = Square(5)\n",
" print('Square: %s' % s)\n",
" print('Area is a %s' % Square.area.__doc__)\n",
" print('Area: %s' % s.area)\n",
" s.area = 36\n",
" del s.area\n"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"{'fset': <function fset at 0x038D66F0>, 'fdel': <function fdel at 0x038D6870>, 'fget': <function fget at 0x038D6AF0>}\n",
"Square: <__main__.Square object at 0x03942450>\n",
"Area is a property of Square defined using nested methods\n",
"Area: 25\n",
"Can't change the area.\n",
"Can't delete the area.\n"
]
}
],
"prompt_number": 9
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Simple Data Descriptor"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"'''A simple data descriptor'''\n",
"\n",
"class DataDescriptor(object):\n",
" '''\n",
" A simple descriptor. Descriptors are useful for giving the user\n",
" of the class the feeling they are using normal get a set methods\n",
" whereas under the hood the set and get methods are altered\n",
" '''\n",
" def __init__(self):\n",
" self.value = 0\n",
" \n",
" def __get__(self, instance, cls):\n",
" '''\n",
" Normal get method behavior looks for the attribute in the\n",
" instance dict then if not found the class dict\n",
" '''\n",
" print 'data descriptor __get__'\n",
" return self.value\n",
" \n",
" def __set__(self, instance, value):\n",
" '''\n",
" Normal set method behavior looks for the attribute in the\n",
" instance dict then if not found the class dict.\n",
" \n",
" This method is altered slightly to try and uppercase the value\n",
" being handed in, if it can't it will just assign the new value\n",
" '''\n",
" print 'data descriptor __set__'\n",
" try:\n",
" self.value = value.upper()\n",
" except AttributeError:\n",
" self.value = value\n",
" \n",
" def __delete__(self, instance):\n",
" '''\n",
" A useful instance for delete would be to delete a line from\n",
" a database.\n",
" '''\n",
" print \"I don't like to be deleted.\"\n"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 12
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class Strange(object):\n",
" '''\n",
" Handles attributes named \"attr\" using the DataDescriptor class\n",
" All other attributes are handled normally.\n",
" \n",
" Interacting with the value of \"attr\" will call the DataDescriptor\n",
" class get, set, or delete methods\n",
" '''\n",
" attr = DataDescriptor()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 13
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s = Strange()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 14
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 15,
"text": [
"0"
]
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"type(s).__dict__['attr'].__get__(s, type(s))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 18,
"text": [
"0"
]
}
],
"prompt_number": 18
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"Strange.__dict__['attr'].__get__(s, Strange)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 19,
"text": [
"0"
]
}
],
"prompt_number": 19
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.attr = 12"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __set__\n"
]
}
],
"prompt_number": 20
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 21,
"text": [
"12"
]
}
],
"prompt_number": 21
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.attr = 'Hello'"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __set__\n"
]
}
],
"prompt_number": 22
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 23,
"text": [
"'HELLO'"
]
}
],
"prompt_number": 23
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.new = 14"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 24
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"s.__dict__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 25,
"text": [
"{'new': 14}"
]
}
],
"prompt_number": 25
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"Strange.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"data descriptor __get__\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 26,
"text": [
"'HELLO'"
]
}
],
"prompt_number": 26
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Non Data Descriptor"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class NonDataDescriptor(object):\n",
" '''\n",
" A data descriptor class without the set and delete methods\n",
" overridden is a non data descriptor class\n",
" '''\n",
" def __init__(self):\n",
" self.value = 0\n",
" \n",
" def __get__(self, instance, cls):\n",
" print('non data descriptor')\n",
" return self.value + 10\n",
" "
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 27
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class AddTen(object):\n",
" ''''''\n",
" attr = NonDataDescriptor()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 28
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ten = AddTen()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 29
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ten.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"non data descriptor\n"
]
},
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 30,
"text": [
"10"
]
}
],
"prompt_number": 30
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ten.__dict__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 31,
"text": [
"{}"
]
}
],
"prompt_number": 31
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ten.attr = 20"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 32
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ten.__dict__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 33,
"text": [
"{'attr': 20}"
]
}
],
"prompt_number": 33
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"AddTen.__dict__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 34,
"text": [
"<dictproxy {'__dict__': <attribute '__dict__' of 'AddTen' objects>,\n",
" '__doc__': None,\n",
" '__module__': '__main__',\n",
" '__weakref__': <attribute '__weakref__' of 'AddTen' objects>,\n",
" 'attr': <__main__.NonDataDescriptor at 0x38d1370>}>"
]
}
],
"prompt_number": 34
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"descriptor `__get__` is called everytime the attribute is accessed, in this example `__getattr__` will override this behavior."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class Overridden(object):\n",
" '''\n",
" Override the getattr method\n",
" '''\n",
" attr = DataDescriptor()\n",
" def __getattribute__(self, name):\n",
" '''\n",
" getattr is only accessed when an attribute is found\n",
" unlike get which is accessed when an attribute is/isn't found\n",
" \n",
" Be careful overriding getattr; strange recursion behavior may result\n",
" '''\n",
" print('no way')"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 35
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"o = Overridden()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 36
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"o.attr"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"no way\n"
]
}
],
"prompt_number": 37
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def func():\n",
" pass"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 38
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"func.__get__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 40,
"text": [
"<method-wrapper '__get__' of function object at 0x038D6CB0>"
]
}
],
"prompt_number": 40
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Every time you use classes and instances with functions you are using non data descriptors"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class A(object):\n",
" def func(self):\n",
" pass"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 41
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a = A()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 42
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Python uses non data descriptors (method-wrapper `__get__`) to bind functions to their parent class"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a.func"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 43,
"text": [
"<bound method A.func of <__main__.A object at 0x03938750>>"
]
}
],
"prompt_number": 43
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"A.func"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 44,
"text": [
"<unbound method A.func>"
]
}
],
"prompt_number": 44
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"type(A.func)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 45,
"text": [
"instancemethod"
]
}
],
"prompt_number": 45
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"type(a.func)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 46,
"text": [
"instancemethod"
]
}
],
"prompt_number": 46
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a.func()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 47
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Trying to access an unbound function on a class will result in an error"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"A.func()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "unbound method func() must be called with A instance as first argument (got nothing instead)",
"output_type": "pyerr",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-48-733ae5a20fb4>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mA\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m: unbound method func() must be called with A instance as first argument (got nothing instead)"
]
}
],
"prompt_number": 48
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"A.func(a)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 49
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Every class instance shares a data store, this may or may not be desirable."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"'''Every class shares a data store'''\n",
"\n",
"from __future__ import print_function\n",
"\n",
"\n",
"class DescriptorClassStorage(object):\n",
" '''Descriptor storing data in the class'''\n",
" \n",
" def __init__(self, default=None):\n",
" self.value = default\n",
" \n",
" def __get__(self, instance, owner):\n",
" return self.value\n",
" \n",
" def __set__(self, instance, value):\n",
" self.value = value\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" class StoreClass(object):\n",
" '''All instances will share \"attr\" data'''\n",
" attr = DescriptorClassStorage(10)\n",
" \n",
" store1 = StoreClass()\n",
" store2 = StoreClass()\n",
" print('store1.attr', store1.attr)\n",
" print('store2.attr', store2.attr)\n",
" print('Setting store1.attr only')\n",
" store1.attr = 100\n",
" print('store1.attr', store1.attr)\n",
" print('store2.attr', store2.attr)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"store1.attr 10\n",
"store2.attr 10\n",
"Setting store1.attr only\n",
"store1.attr 100\n",
"store2.attr 100\n"
]
}
],
"prompt_number": 51
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Because descriptors keep track of which instance owns what data it is necessary to label attributes to keep data seperate. This example uses getattr and uuid as a key to access the data and avoid name clashes."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"'''Every instance stores it's own data'''\n",
"\n",
"from __future__ import print_function\n",
"\n",
"import uuid\n",
"\n",
"\n",
"class DescriptorInstanceStorage(object):\n",
" '''Descriptor that stores attribute data in the instance'''\n",
" \n",
" def __init__(self, default=None):\n",
" self.hidden_name = '__' + uuid.uuid4().hex\n",
" self.default = default\n",
" \n",
" def __get__(self, instance, owner):\n",
" print(self.hidden_name)\n",
" return getattr(instance, self.hidden_name, self.default)\n",
" \n",
" def __set__(self, instance, value):\n",
" setattr(instance, self.hidden_name, value)\n",
" \n",
"if __name__ == '__main__':\n",
" class StoreInstance(object):\n",
" '''All instances have their own \"attr\"'''\n",
" attr = DescriptorInstanceStorage(10)\n",
" \n",
" store1 = StoreInstance()\n",
" store2 = StoreInstance()\n",
" print('store1.attr', store1.attr)\n",
" print('store2.attr', store2.attr)\n",
" print('Setting store1.attr only')\n",
" store1.attr = 100\n",
" print('store1.attr', store1.attr)\n",
" print('store2.attr', store2.attr)\n"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"__380bd46902834f97ab7a00dbcab6ed2f\n",
"store1.attr 10\n",
"__380bd46902834f97ab7a00dbcab6ed2f\n",
"store2.attr 10\n",
"Setting store1.attr only\n",
"__380bd46902834f97ab7a00dbcab6ed2f\n",
"store1.attr 100\n",
"__380bd46902834f97ab7a00dbcab6ed2f\n",
"store2.attr 10\n"
]
}
],
"prompt_number": 61
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"store1.__dict__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 60,
"text": [
"{'__186bb650a6924b60bce867f4d48d6f66': 100}"
]
}
],
"prompt_number": 60
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"'''Example use case for descriptor that checks conditions on attribute'''\n",
"\n",
"from __future__ import print_function\n",
"\n",
"import uuid\n",
"\n",
"\n",
"class Checked(object):\n",
" '''\n",
" Descriptor that checks with user-supplied check function if an\n",
" attribute is valid.\n",
" '''\n",
" \n",
" def __init__(self, checker=None, default=None):\n",
" if checker:\n",
" # Checker must be a callable\n",
" checker(default)\n",
" self.hidden_name = '__' + uuid.uuid4().hex\n",
" self.checker = checker\n",
" self.default = default\n",
" \n",
" def __get__(self, instance, owner):\n",
" return getattr(instance, self.hidden_name, self.default)\n",
" \n",
" def __set__(self, instance, value):\n",
" if self.checker:\n",
" self.checker(value)\n",
" setattr(instance, self.hidden_name, value)\n",
" \n",
"if __name__ == '__main__':\n",
" \n",
" def is_int(value):\n",
" '''Check if value is an integer'''\n",
" if not isinstance(value, int):\n",
" raise ValueError('Int required')\n",
" \n",
" class Restricted(object):\n",
" '''Use checked attributes'''\n",
" attr1 = Checked(checker=is_int, default=10)\n",
" attr2 = Checked(default=12.5)\n",
" \n",
" try:\n",
" attr3 = Checked(checker=is_int, default=12.5)\n",
" except ValueError:\n",
" print('Cannot set default to float, must be int')\n",
" attr3 = Checked(checker=is_int, default=12)\n",
" \n",
" restricted = Restricted()\n",
" print('attr1', restricted.attr1)\n",
" restricted.attr1 = 100\n",
" print('attr1', restricted.attr1)\n",
" try:\n",
" restricted.attr1 = 200.12\n",
" except ValueError:\n",
" print('Cannot set attr1 to float, must be int')\n",
" restricted.attr1 = 200\n",
" print('attr1', restricted.attr1)\n",
" \n",
" "
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Cannot set default to float, must be int\n",
"attr1 10\n",
"attr1 100\n",
"Cannot set attr1 to float, must be int\n",
"attr1 200\n"
]
}
],
"prompt_number": 64
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To simplify access and separation of instance data use the instance as a key in a WeakKeyDictionary."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from __future__ import print_function\n",
"\n",
"\n",
"from weakref import WeakKeyDictionary\n",
"\n",
"\n",
"class Checked(object):\n",
" '''\n",
" Descriptor that checks with user-supplied check function if an\n",
" attribute is valid.\n",
" '''\n",
" def __init__(self, default, checker=None):\n",
" self.default = default\n",
" self.data = WeakKeyDictionary()\n",
" self.checker = checker\n",
" if self.checker and callable(self.checker):\n",
" self.checker(self.default)\n",
" \n",
" def __get__(self, instance, owner):\n",
" return self.data.get(instance, self.default)\n",
" \n",
" def __set__(self, instance, value):\n",
" if self.checker:\n",
" self.checker(value)\n",
" self.data[instance] = value\n",
"\n",
" \n",
"def isInt(value):\n",
" if not isinstance(value, int):\n",
" raise ValueError('Int required')\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" \n",
" class Restricted(object):\n",
" '''Use checked attributes'''\n",
" attr1 = Checked(10, checker=isInt)\n",
" attr2 = Checked(12.5)\n",
" \n",
" try:\n",
" attr3 = Checked(12.5, checker=isInt)\n",
" except ValueError:\n",
" print('Cannot set default to float, must be int')\n",
" attr3 = Checked(12, checker=isInt)\n",
" \n",
" restricted = Restricted()\n",
" print('attr1', restricted.attr1)\n",
" restricted.attr1 = 100\n",
" print('attr1', restricted.attr1)\n",
" try:\n",
" restricted.attr1 = 200.12\n",
" except ValueError:\n",
" print('Cannot set attr1 to float, must be int')\n",
" restricted.attr1 = 200\n",
" print('attr1', restricted.attr1)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Cannot set default to float, must be int\n",
"attr1 10\n",
"attr1 100\n",
"Cannot set attr1 to float, must be int\n",
"attr1 200\n"
]
}
],
"prompt_number": 11
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"#This makes everything pretty\n",
"\n",
"from IPython.core.display import HTML\n",
"from urllib import urlopen\n",
"def css_styling():\n",
" styles = open('styles/custom.css', 'r').read()\n",
" return HTML(styles)\n",
"css_styling()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<style>\r\n",
" @font-face {\r\n",
" font-family: \"Computer Modern\";\r\n",
" src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunss.otf');\r\n",
" }\r\n",
" @font-face {\r\n",
" font-family: \"Computer Modern\";\r\n",
" font-weight: bold;\r\n",
" src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunsx.otf');\r\n",
" }\r\n",
" @font-face {\r\n",
" font-family: \"Computer Modern\";\r\n",
" font-style: oblique;\r\n",
" src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunsi.otf');\r\n",
" }\r\n",
" @font-face {\r\n",
" font-family: \"Computer Modern\";\r\n",
" font-weight: bold;\r\n",
" font-style: oblique;\r\n",
" src: url('http://9dbb143991406a7c655e-aa5fcb0a5a4ec34cff238a2d56ca4144.r56.cf5.rackcdn.com/cmunso.otf');\r\n",
" }\r\n",
" div.cell{\r\n",
" width:800px;\r\n",
" margin-left:16% !important;\r\n",
" margin-right:auto;\r\n",
" }\r\n",
" h1 {\r\n",
" font-family: Helvetica, serif;\r\n",
" }\r\n",
" h4{\r\n",
" margin-top:12px;\r\n",
" margin-bottom: 3px;\r\n",
" }\r\n",
" div.text_cell_render{\r\n",
" font-family: Computer Modern, \"Helvetica Neue\", Arial, Helvetica, Geneva, sans-serif;\r\n",
" line-height: 145%;\r\n",
" font-size: 130%;\r\n",
" width:800px;\r\n",
" margin-left:auto;\r\n",
" margin-right:auto;\r\n",
" }\r\n",
" .CodeMirror{\r\n",
" font-family: \"Source Code Pro\", source-code-pro,Consolas, monospace;\r\n",
" }\r\n",
" .prompt{\r\n",
" display: None;\r\n",
" }\r\n",
" .text_cell_render h5 {\r\n",
" font-weight: 300;\r\n",
" font-size: 22pt;\r\n",
" color: #4057A1;\r\n",
" font-style: italic;\r\n",
" margin-bottom: .5em;\r\n",
" margin-top: 0.5em;\r\n",
" display: block;\r\n",
" }\r\n",
" \r\n",
" .warning{\r\n",
" color: rgb( 240, 20, 20 )\r\n",
" } \r\n",
"</style>\r\n",
"<script>\r\n",
" MathJax.Hub.Config({\r\n",
" TeX: {\r\n",
" extensions: [\"AMSmath.js\"]\r\n",
" },\r\n",
" tex2jax: {\r\n",
" inlineMath: [ ['$','$'], [\"\\\\(\",\"\\\\)\"] ],\r\n",
" displayMath: [ ['$$','$$'], [\"\\\\[\",\"\\\\]\"] ]\r\n",
" },\r\n",
" displayAlign: 'center', // Change this to 'center' to center equations.\r\n",
" \"HTML-CSS\": {\r\n",
" styles: {'.MathJax_Display': {\"margin\": 4}}\r\n",
" }\r\n",
" });\r\n",
"</script>\r\n"
],
"metadata": {},
"output_type": "pyout",
"prompt_number": 1,
"text": [
"<IPython.core.display.HTML at 0x23f1490>"
]
}
],
"prompt_number": 1
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment