Skip to content

Instantly share code, notes, and snippets.

@jlesquembre
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jlesquembre/9994132 to your computer and use it in GitHub Desktop.
Save jlesquembre/9994132 to your computer and use it in GitHub Desktop.
Descriptors pyug
{
"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