Skip to content

Instantly share code, notes, and snippets.

@coady
Last active April 15, 2016 20:07
Show Gist options
  • Save coady/28ded31fe67f8da4ccf0 to your computer and use it in GitHub Desktop.
Save coady/28ded31fe67f8da4ccf0 to your computer and use it in GitHub Desktop.
Python descriptors.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Descriptors\n",
"Demonstrates advanced usage of Python descriptors, through a variety of useful property decorators."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A read-only class property. Alternatively, a property can be defined on a metaclass."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class classproperty(property):\n",
" def __get__(self, instance, owner):\n",
" return self.fget(owner)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Foo"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Foo(object):\n",
" @classproperty\n",
" def bar(cls):\n",
" return cls\n",
"\n",
"Foo.bar"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Foo"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Foo().bar"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A property which caches its value upon first invocation. It doesn't subclass property, because properties override the stored attribute lookup."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class cachedproperty(staticmethod):\n",
" def __get__(self, instance, owner):\n",
" if instance is None:\n",
" return self\n",
" value = self.__func__(instance)\n",
" setattr(instance, self.__func__.__name__, value)\n",
" return value"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"<__main__.cachedproperty at 0x10697e050>"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Foo(object):\n",
" @cachedproperty\n",
" def bar(self):\n",
" return object()\n",
"\n",
"Foo.bar"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = Foo()\n",
"f.bar is f.bar"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A method which is static when accessed through the class. In python 2, unbound methods restrict the type of the self argument. It served no purpose other than being restrictive, so it was removed in python 3."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class py3method(staticmethod):\n",
" def __get__(self, instance, owner):\n",
" if instance is None:\n",
" return self.__func__\n",
" return self.__func__.__get__(instance, owner)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Foo(object):\n",
" @py3method\n",
" def bar(self):\n",
" return self\n",
"\n",
"Foo.bar(True)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"<__main__.Foo at 0x10697a410>"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Foo().bar()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A property which can be used as a classmethod as well. The instance's attributes are used as the input parameters."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class hybridproperty(property):\n",
" def __get__(self, instance, owner):\n",
" if instance is None:\n",
" return self.fget.__get__(owner)\n",
" code = self.fget.func_code\n",
" args = code.co_varnames[1:code.co_argcount]\n",
" return self.fget(owner, *(getattr(instance, arg) for arg in args))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Foo(object):\n",
" def __init__(self, arg):\n",
" self.arg = arg\n",
"\n",
" @hybridproperty\n",
" def bar(cls, arg):\n",
" return arg\n",
"\n",
"Foo.bar(True)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Foo(True).bar"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A class whose attributes bind to the enclosing instance (or class), effectively making the inner class a namespace. The metaclass stores the outer objects upon class access, and binds to them upon subsequent method access."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import threading\n",
"\n",
"class namespace(type):\n",
" class method(staticmethod):\n",
" def __get__(self, instance, owner):\n",
" return self.__func__.__get__(*owner.local.objects)\n",
"\n",
" def __init__(self, name, bases, namespace):\n",
" type.__init__(self, name, bases, namespace)\n",
" for name, value in namespace.items():\n",
" if hasattr(value, '__get__'):\n",
" setattr(self, name, type(self).method(value))\n",
" self.local = threading.local()\n",
"\n",
" def __get__(self, instance, owner):\n",
" self.local.objects = instance, owner\n",
" return self"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Foo(object):\n",
" class bar(object):\n",
" __metaclass__ = namespace\n",
"\n",
" def method(self):\n",
" return self\n",
"\n",
" @classmethod\n",
" def clsmethod(cls):\n",
" return cls\n",
"\n",
"f = Foo()\n",
"f.bar is Foo.bar"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"__main__.Foo"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Foo.bar.clsmethod()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"<__main__.Foo at 0x106958b90>"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.bar.method()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment