Skip to content

Instantly share code, notes, and snippets.

@holdenweb
Created September 29, 2016 12:17
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 holdenweb/dd097fbdde743a4c88b4fd24ab42b292 to your computer and use it in GitHub Desktop.
Save holdenweb/dd097fbdde743a4c88b4fd24ab42b292 to your computer and use it in GitHub Desktop.
Traits Early Experiments 2
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### A Quick Introduction to Traits\n",
"\n",
"Traits aren't very well-documented.\n",
"Since they are ptentially extremely useful for \"software plumbing\"\n",
"this short notebook in intended to familiarise you with their behaviour.\n",
"\n",
"First let me show you how up-to-date I am:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"scrolled": true
},
"outputs": [],
"source": [
"import sys; sys.version"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since the Jupyter Notebook (and the IPython system generally) use\n",
"the `traitlets` module, a lightweight implementation, we will too.\n",
"\n",
"We being by making the whole module available, then we import a few specific names."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import traitlets as tts\n",
"from traitlets import HasTraits, Unicode, Integer, Float, observe, All"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To declare a class with traits, it must inherit from `traitlets.HasTraits`.\n",
"Adding a trait to a class is simply a matter of binding a new Trait\n",
"object to a class variable·\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class MyClass1(HasTraits):\n",
" i = Integer(0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This ensures that _all_ instances of the `MyClass1` class have an `i` attribute, initially 0."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc1_1 = MyClass1()\n",
"myc1_1.i"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now look what happens if we try to assign it a string value.\n",
"I'm catching the exception to save you having to go through the traceback."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"try:\n",
" myc1_1.i = \"banana\"\n",
"except tts.TraitError as e:\n",
" print(\"Trait error:\", e)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So traits give us a simple but effective _typing_ capability.\n",
"Naturally there are mechanisms available for you to declare\n",
"your own traits to support the types you use in your code.\n",
"We'll get to that later."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Furthermore, a class can monitor its traits and take action on changes of value.\n",
"To monitor a trait from within its class, use the `@observe` decorator to\n",
"decorate a method"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class MyClass2(MyClass1):\n",
" f = Float(32.0)\n",
" u = Unicode(\"Python!\")\n",
" @observe(\"f\")\n",
" def _f_changed(self, c):\n",
" print(\"f changed:\", c)\n",
" print(\"traits now - i:\", self.i, 'f:', self.f, 'u:', self.u)\n",
"\n",
"myc2_1 = MyClass2()\n",
"print(myc2_1.f)\n",
"myc2_1.f = 44.0\n",
"print(\"*\", myc2_1._trait_notifiers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also observe changes to the traits on an instance externally."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def report_change(s):\n",
" def r_c(c):\n",
" print(s, \"changes:\", c)\n",
" return r_c\n",
"_i_changed = report_change(\"INSTANCE\")\n",
"print(\"*\", myc2_1._trait_notifiers)\n",
"myc2_1.observe(_i_changed, \"i\")\n",
"print(\"*\", myc2_1._trait_notifiers)\n",
"myc2_1.observe(_i_changed, \"i\")\n",
"print(\"*\", myc2_1._trait_notifiers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that an observer cannot be attached to the same trait twice."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_1._trait_notifiers"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_1.i = 342"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also remove observers by calling `unobserve` with the same arguments."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_1.unobserve(_i_changed, 'i')\n",
"myc2_1.i = 341"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"myc2_2 = MyClass2(i=100)\n",
"myc2_2.i = 0"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_2.f = 1.1412\n",
"myc2_2.u = \"Perl\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"myc2_3 = MyClass2(i=99)\n",
"myc2_3.i = 21"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When you instantiate a class that has traits, you can set the\n",
"trait values as keyword arguments to the instantiation call.\n",
"\n",
"The assignment of these values will be recorded as a change from the default value if the trait is being observed.\n",
"Multiple observations will be correctly handled."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_4 = MyClass2(i=100, u=\"Steve\")\n",
"def changes(c):\n",
" print(\"Changes:\", c)\n",
"\n",
"myc2_4.observe(changes, All)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_4.u = \"Some value\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_4.f = 3.14159"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_4.i = 4"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Classes (and, for that matter, instances) can report on the traits they have.\n",
"These methods return a dict in which trait names are the keys, and the traits are the values."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"MyClass2.class_traits()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_1.traits()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the instance traits are the same as the class traits."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_1.f = 3.33333"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This ability to implement the _observer_ pattern is valuable to provide\n",
"all kinds of standard plumbing in software systems."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"myc2_4.trait_metadata(\"i\", 1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class MyCounter(HasTraits):\n",
" i = Integer()\n",
"\n",
"from threading import Thread\n",
"from time import sleep\n",
"\n",
"class MyThread(Thread):\n",
" def __init__(self, counter):\n",
" Thread.__init__(self)\n",
" self.counter = MyCounter()\n",
" def run(self):\n",
" while True:\n",
" sleep(1)\n",
" self.counter.i += 1\n",
"\n",
"thread = MyThread(MyCounter())\n",
"thread.start()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"thread.counter.i"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def report_count(c):\n",
" print(\"Counter now\", c['new'])\n",
"thread.counter.observe(report_count, \"i\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"thread.counter.unobserve(report_count, \"i\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"thread.counter.i = 1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"help(observe)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"help(thread.counter.observe)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.7.0a0",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0a0"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment