jdc - Jupyter Dynamic Classes¶
Alex Hagen
jdc
is a Jupyter magic made to solve a very specific problem: that
sometimes you want to define classes dynamically in Jupyter notebooks,
and you want it to look good. On Jupyter’s
Github there’s a
lengthy discussion and wider view of this issue, but I’ve implemented a
quickfix which I think ended up looking pretty semantic. Thanks to Igor
Sobreira
for the underlying IPython.
The example below will be most informative, but basically all jdc
allows you to do is to add a cell and define
%%add_to our_class
def our_function(self, our_variable):
print our_variable
and that will add the method our_function
to our_class
, whether
our_class
is a class, or an object with a class type.
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Example¶
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Below is an example on how to use
jdc
. First we have to import the class:
In [1]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
import jdc
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Then, we have to make a dummy class:
In [2]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
class dog(object): def __init__(self, name, noise): self.name = name self.noise = noise rover = dog('Rover', 'ruff') spot = dog('Spot', 'woof')
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Then, we want to add a function to that class:
In [3]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
%%add_to dog def bark(self, times=1): saying = ('%s ' % self.noise) * times print("%s says: %s" % (self.name, saying)) def bark2(self, times=1): saying = ('%s ' % self.noise) * times print("%s says: %s" % (self.name, saying))
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
And now we can access that function from any method of that class.
In [4]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
rover.bark(1) spot.bark(2)
Rover says: ruff
Spot says: woof woof
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
If we want to add a function to only one object of that class, we can do that, as well:
In [5]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
%%add_to spot def sit(self): print('%s is now sitting' % self.name)
In [6]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
spot.sit()
Spot is now sitting
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Note that the function added to a class object is only available to that object, not all objects of that class:
In [7]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
try: rover.sit() except AttributeError: print("%s doesn't know that trick" % rover.name)
Rover doesn't know that trick
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
And if we’re writing a lot of code, we can now do that in a single cell.
In [8]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
%%add_to spot def rollover(self): print("%s rolled over" % self.name) def highfive(self): print("%s is trying to high five you" % self.name) def domytaxes(self): print("%s is just showing off now" % self.name)
In [9]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
spot.rollover() spot.highfive() spot.domytaxes()
Spot rolled over
Spot is trying to high five you
Spot is just showing off now
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
Nesting functions and decorators should now work. Notice when calling the wrapped function that we must call it by
func(self, *args)
instead ofself.func(*args)
orfunc(*args)
.
In [10]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
%%add_to dog def race(func): def wrapper(self): import time t1 = time.time() func(self) t2 = time.time() print("A new record time, %s finished in: " % self.name + str((t2 - t1)) + " seconds!\n") return wrapper @race def race_course(self): print("%s finished the course." % self.name)
In [11]:
jp-CodeMirrorEditor jp-Editor jp-InputArea-editor
spot.race_course()
Spot finished the course.
A new record time, Spot finished in: 5.984306335449219e-05 seconds!
jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput
and that’s it.
Installation¶
Right now, installation should now work through PyPI:
pip install jdc
Please open up an issue if it doesn’t work, I’ll fix it!
Note¶
@dsblank has pointed this out on the Jupyter discussion:
you can do this in regular Python, without resorting to any extra magic:
Cell 1:
class MyClass(): def method1(self): print(“method1”) Cell 2:
class MyClass(MyClass): def method2(self): print(“method2”) Cell 3:
instance = MyClass() instance.method1() instance.method2() That is, you can define a class recursively, cell by cell. I don’t think the final class is any different from one defined all in one cell. So you don’t need an extra package to “solve” this problem.
I personally like the semantics of jdc
, so I won’t be removing it
from PyPI, but it’s always good to know alternatives, thanks @dsblank!