Last active
August 29, 2015 13:58
-
-
Save jlesquembre/9994132 to your computer and use it in GitHub Desktop.
Descriptors pyug
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"metadata": { | |
"celltoolbar": "Slideshow", | |
"name": "", | |
"signature": "sha256:b392c503122c5ac4b5ed7cb8d3ce0d2ba9fce4f4d8596d0ed399df0e06bc778a" | |
}, | |
"nbformat": 3, | |
"nbformat_minor": 0, | |
"worksheets": [ | |
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"Descriptors\n", | |
"-----------\n", | |
"\n", | |
"A pythonic alternative to getters and setters" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"First some background knowledge\n", | |
"-------------------------------" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Circle:\n", | |
" PI = 3.14\n", | |
" def __init__(self, radius):\n", | |
" self.radius = radius\n", | |
"\n", | |
"mycircle = Circle(2)\n", | |
"mycircle.radius" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "-" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 1, | |
"text": [ | |
"2" | |
] | |
} | |
], | |
"prompt_number": 1 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"mycircle.PI" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "-" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 2, | |
"text": [ | |
"3.14" | |
] | |
} | |
], | |
"prompt_number": 2 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"In python, the attribute objects are stored in a special attribute called `__dict__`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"mycircle.__dict__" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 3, | |
"text": [ | |
"{'radius': 2}" | |
] | |
} | |
], | |
"prompt_number": 3 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"This is equivalent:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"mycircle.__dict__['radius'] is mycircle.radius" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 4, | |
"text": [ | |
"True" | |
] | |
} | |
], | |
"prompt_number": 4 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"mycircle.__dict__['radius'] = 3\n", | |
"mycircle.radius" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 5, | |
"text": [ | |
"3" | |
] | |
} | |
], | |
"prompt_number": 5 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"If you try to access an attribute object, which doesn't exist, the class level attributes are used as fallback" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Circle:\n", | |
" PI = 3.14\n", | |
" def __init__(self, radius):\n", | |
" self.radius = radius\n", | |
"\n", | |
"mycircle = Circle(2)\n", | |
"mycircle.PI is Circle.__dict__['PI']" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 6, | |
"text": [ | |
"True" | |
] | |
} | |
], | |
"prompt_number": 6 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"Circle.__dict__" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 7, | |
"text": [ | |
"mappingproxy({'PI': 3.14, '__dict__': <attribute '__dict__' of 'Circle' objects>, '__init__': <function Circle.__init__ at 0x7f4c201617a0>, '__weakref__': <attribute '__weakref__' of 'Circle' objects>, '__module__': '__main__', '__doc__': None})" | |
] | |
} | |
], | |
"prompt_number": 7 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"dict_proxy is used by Python where you need a dict but don\u2019t want to allow modifications. " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"Rules for accessing an attribute on an object like `obj.foo` gets\n", | |
"\n", | |
"1. Value in `obj.__dict__` if it exists\n", | |
"\n", | |
"2. Value in `type(obj).__dict__`\n", | |
"\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"An assignment always creates an entry in `obj.__dict__`" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"But this rules are incomplete... \n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"What is a descriptor?\n", | |
"---------------------\n", | |
"A descriptor is any object that implements at least one of methods named `__get__()`, `__set__()`, and `__delete__()`\n", | |
"\n", | |
"The signature of `__get__`, `__set__` and `__del__` are fixed:\n", | |
"\n", | |
"`def __get__(self, obj, type=None) --> value`\n", | |
"\n", | |
"`def __set__(self, obj, value) --> None`\n", | |
"\n", | |
"`def __delete__(self, obj) --> None`\n", | |
"\n", | |
"\n", | |
"data descriptor vs non-data descriptor\n", | |
"--------------------------------------\n", | |
"\n", | |
"A **data descriptor** implements both `__get__()` and `__set__()`. \n", | |
"Implementing only `__get__()` makes you a **non-data descriptor**.\n", | |
"\n", | |
"\n", | |
"See https://docs.python.org/3.4/howto/descriptor.html#descriptor-protocol\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"Rules with descriptors\n", | |
"----------------------\n", | |
"\n", | |
"1. Result of the `__get__` method of the **data descriptor** of the same name attached to the class if it exists\n", | |
"\n", | |
"2. Value in `obj.__dict__` if it exists\n", | |
"\n", | |
"3. Result of the `__get__` method of the **non-data descriptor** of the same name on the class\n", | |
"\n", | |
"3. Value in `type(obj).__dict__`\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"An assignment always creates an entry in `obj.__dict__`\n", | |
"\n", | |
"Unless the **descriptor** has a `__set__` method, in which case you\u2019re calling a function" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Decimal: \n", | |
" def __get__(self, obj, type=None):\n", | |
" print ('__get__') \n", | |
"\n", | |
" def __set__(self, obj, value):\n", | |
" print ('__set__ value {}'.format(value))\n", | |
" \n", | |
" \n", | |
"class Circle:\n", | |
" PI = 3.14\n", | |
" radius = Decimal()\n", | |
" \n", | |
" def __init__(self, radius):\n", | |
" self.radius = radius\n", | |
" \n", | |
"\n", | |
"mycircle = Circle(2)\n", | |
"mycircle.radius\n" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"stream": "stdout", | |
"text": [ | |
"__set__ value 2\n", | |
"__get__\n" | |
] | |
} | |
], | |
"prompt_number": 8 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Decimal:\n", | |
" def __init__(self, name):\n", | |
" self.name = name\n", | |
" \n", | |
" def __get__(self, obj, type=None):\n", | |
" return obj.__dict__[self.name]\n", | |
"\n", | |
" def __set__(self, obj, value): \n", | |
" obj.__dict__[self.name] = value\n", | |
" \n", | |
" \n", | |
"class Circle:\n", | |
" PI = 3.14\n", | |
" radius = Decimal('radius')\n", | |
" def __init__(self, radius):\n", | |
" self.radius = radius\n", | |
" \n", | |
"\n", | |
"mycircle = Circle(2)\n", | |
"mycircle.radius" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 9, | |
"text": [ | |
"2" | |
] | |
} | |
], | |
"prompt_number": 9 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Decimal:\n", | |
" def __init__(self, name):\n", | |
" self.name = name\n", | |
" \n", | |
" def __get__(self, obj, type=None):\n", | |
" return round(obj.__dict__[self.name], 2)\n", | |
"\n", | |
" def __set__(self, obj, value): \n", | |
" obj.__dict__[self.name] = float(value)\n", | |
" \n", | |
" \n", | |
"class Circle:\n", | |
" PI = 3.14\n", | |
" radius = Decimal('radius')\n", | |
" def __init__(self, radius):\n", | |
" self.radius = radius\n", | |
" \n", | |
"\n", | |
"mycircle = Circle('2.12133')\n", | |
"mycircle.radius" | |
], | |
"language": "python", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 10, | |
"text": [ | |
"2.12" | |
] | |
} | |
], | |
"prompt_number": 10 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"Non-data descriptor example\n", | |
"---------------------------" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class cached_property(object):\n", | |
" def __init__(self, func):\n", | |
" self._func = func\n", | |
" self.__name__ = func.__name__\n", | |
"\n", | |
" def __get__(self, obj, klass):\n", | |
" print (\"Called the func\")\n", | |
" result = self._func(obj)\n", | |
" obj.__dict__[self.__name__] = result\n", | |
" return result\n", | |
"\n", | |
"class MyClass(object):\n", | |
" @cached_property\n", | |
" def x(self):\n", | |
" return 42\n", | |
" \n", | |
"obj = MyClass()\n", | |
"\n", | |
"print(obj.x)\n", | |
"print(obj.x)\n" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"stream": "stdout", | |
"text": [ | |
"Called the func\n", | |
"42\n", | |
"42\n" | |
] | |
} | |
], | |
"prompt_number": 11 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"References\n", | |
"----------\n", | |
"\n", | |
"https://docs.python.org/3.4/howto/descriptor.html\n", | |
"\n", | |
"http://simeonfranklin.com/blog/2013/nov/17/descriptors-talk-sf-python/\n", | |
"\n", | |
"Luciano Ramalho at Pycon 2013 on descriptors - http://pyvideo.org/video/1760/encapsulation-with-descriptors\n", | |
"\n", | |
"David Beazley on cool advanced OOP stuff in Python 3 - http://pyvideo.org/video/1716/python-3-metaprogramming\n" | |
] | |
} | |
], | |
"metadata": {} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment