Created
September 29, 2016 12:17
-
-
Save holdenweb/dd097fbdde743a4c88b4fd24ab42b292 to your computer and use it in GitHub Desktop.
Traits Early Experiments 2
This file contains 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
{ | |
"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