@ -13,6 +13,11 @@
# Imports
#-----------------------------------------------------------------------------
try : # Python >= 3.3
from inspect import signature , Parameter
except ImportError :
from IPython . utils . signatures import signature , Parameter
from IPython . html . widgets import ( Widget , TextWidget ,
FloatSliderWidget , IntSliderWidget , CheckboxWidget , DropdownWidget ,
ContainerWidget )
@ -48,13 +53,13 @@ def _get_min_max_value(min, max, value):
elif isinstance ( value , int ) :
min , max = - value , 3 * value
else :
raise TypeError ( ' expected a number, got: %r ' % number )
raise TypeError ( ' expected a number, got: %r ' % value )
else :
raise ValueError ( ' unable to infer range, value from: ( {0} , {1} , {2} ) ' . format ( min , max , value ) )
return min , max , value
def _widget_abbrev ( o ) :
def _widget_abbrev_single_value ( o ) :
""" Make widgets from single values, which can be used written as parameter defaults. """
if isinstance ( o , string_types ) :
return TextWidget ( value = unicode_type ( o ) )
elif isinstance ( o , dict ) :
@ -72,6 +77,9 @@ def _widget_abbrev(o):
elif isinstance ( o , int ) :
min , max , value = _get_min_max_value ( None , None , o )
return IntSliderWidget ( value = o , min = min , max = max )
def _widget_abbrev ( o ) :
""" Make widgets from abbreviations: single values, lists or tuples. """
if isinstance ( o , ( list , tuple ) ) :
if _matches ( o , ( int , int ) ) :
min , max , value = _get_min_max_value ( o [ 0 ] , o [ 1 ] , None )
@ -92,9 +100,45 @@ def _widget_abbrev(o):
return DropdownWidget ( value = unicode_type ( o [ 0 ] ) ,
values = [ unicode_type ( k ) for k in o ] )
else :
return _widget_abbrev_single_value ( o )
def _widget_or_abbrev ( value ) :
if isinstance ( value , Widget ) :
return value
widget = _widget_abbrev ( value )
if widget is None :
raise ValueError ( " %r cannot be transformed to a Widget " % value )
return widget
def _widget_for_param ( param , kwargs ) :
""" Get a widget for a parameter.
We look for , in this order :
- keyword arguments passed to interact [ ive ] ( ) that match the parameter name .
- function annotations
- default values
Returns an instance of Widget , or None if nothing suitable is found .
Raises ValueError if the kwargs or annotation value cannot be made into
a widget .
"""
if param . name in kwargs :
return _widget_or_abbrev ( kwargs . pop ( param . name ) )
if param . annotation is not Parameter . empty :
return _widget_or_abbrev ( param . annotation )
if param . default is not Parameter . empty :
# Returns None if it's not suitable
return _widget_abbrev_single_value ( param . default )
return None
def interactive ( f , * * kwargs ) :
""" Interact with a function using widgets. """
""" Build a group of widgets for setting the inputs to a function ."""
co = kwargs . pop ( ' clear_output ' , True )
# First convert all args to Widget instances
@ -102,31 +146,37 @@ def interactive(f, **kwargs):
container = ContainerWidget ( )
container . result = None
container . kwargs = dict ( )
for key , value in kwargs . items ( ) :
if isinstance ( value , Widget ) :
widget = value
else :
widget = _widget_abbrev ( value )
if widget is None :
raise ValueError ( " Object cannot be transformed to a Widget " )
widget . description = key
widgets . append ( ( key , widget ) )
widgets . sort ( key = lambda e : e [ 1 ] . __class__ . __name__ )
container . children = [ e [ 1 ] for e in widgets ]
# Extract parameters from the function signature
for param in signature ( f ) . parameters . values ( ) :
param_widget = _widget_for_param ( param , kwargs )
if param_widget is not None :
param_widget . description = param . name
widgets . append ( param_widget )
# Extra parameters from keyword args - we assume f takes **kwargs
for name , value in sorted ( kwargs . items ( ) , key = lambda x : x [ 0 ] ) :
widget = _widget_or_abbrev ( value )
widget . description = name
widgets . append ( widget )
# This has to be done as an assignment, not using container.children.append,
# so that traitlets notices the update.
container . children = widgets
# Build the callback
def call_f ( name , old , new ) :
actual_kwargs = { }
for key , widget in widgets :
for widget in widgets :
value = widget . value
container . kwargs [ key ] = value
actual_kwargs [ key ] = value
container . kwargs [ widget. description ] = value
actual_kwargs [ widget. description ] = value
if co :
clear_output ( wait = True )
container . result = f ( * * actual_kwargs )
# Wire up the widgets
for key, widget in widgets :
for widget in widgets :
widget . on_trait_change ( call_f , ' value ' )
container . on_displayed ( lambda _ : call_f ( None , None , None ) )
@ -134,6 +184,7 @@ def interactive(f, **kwargs):
return container
def interact ( f , * * kwargs ) :
""" Interact with a function using widgets. """
w = interactive ( f , * * kwargs )
f . widget = w
display ( w )