@ -1,5 +1,4 @@
""" Interact with functions using widgets.
"""
""" Interact with functions using widgets. """
#-----------------------------------------------------------------------------
# Copyright (c) 2013, the IPython Development Team.
@ -13,10 +12,13 @@
# Imports
#-----------------------------------------------------------------------------
from __future__ import print_function
try : # Python >= 3.3
from inspect import signature , Parameter
except ImportError :
from IPython . utils . signatures import signature , Parameter
from inspect import getcallargs
from IPython . html . widgets import ( Widget , TextWidget ,
FloatSliderWidget , IntSliderWidget , CheckboxWidget , DropdownWidget ,
@ -30,6 +32,7 @@ from IPython.utils.py3compat import string_types, unicode_type
def _matches ( o , pattern ) :
""" Match a pattern of types in a sequence. """
if not len ( o ) == len ( pattern ) :
return False
comps = zip ( o , pattern )
@ -49,9 +52,9 @@ def _get_min_max_value(min, max, value):
elif value == 0 :
min , max , value = 0 , 1 , 0
elif isinstance ( value , float ) :
min , max = - value , 3.0 * value
min , max = ( - value , 3.0 * value ) if value > 0 else ( 3.0 * value , - value )
elif isinstance ( value , int ) :
min , max = - value , 3 * value
min , max = ( - value , 3 * value ) if value > 0 else ( 3 * value , - value )
else :
raise TypeError ( ' expected a number, got: %r ' % value )
else :
@ -67,8 +70,6 @@ def _widget_abbrev_single_value(o):
values = o . values ( )
w = DropdownWidget ( value = values [ 0 ] , values = values , labels = labels )
return w
# Special case float and int == 0.0
# get_range(value):
elif isinstance ( o , bool ) :
return CheckboxWidget ( value = o )
elif isinstance ( o , float ) :
@ -77,6 +78,8 @@ def _widget_abbrev_single_value(o):
elif isinstance ( o , int ) :
min , max , value = _get_min_max_value ( None , None , o )
return IntSliderWidget ( value = o , min = min , max = max )
else :
return None
def _widget_abbrev ( o ) :
""" Make widgets from abbreviations: single values, lists or tuples. """
@ -99,92 +102,157 @@ def _widget_abbrev(o):
elif all ( isinstance ( x , string_types ) for x in 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
def _widget_from_abbrev ( abbrev ) :
""" Build a Widget intstance given an abbreviation or Widget. """
if isinstance ( abbrev , Widget ) :
return abbrev
widget = _widget_abbrev ( value )
widget = _widget_abbrev ( abbrev )
if widget is None :
raise ValueError ( " %r cannot be transformed to a Widget " % value )
raise ValueError ( " %r cannot be transformed to a Widget " % abbrev )
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 _yield_abbreviations_for_parameter ( param , args , kwargs ) :
""" Get an abbreviation for a function parameter. """
# print(param, args, kwargs)
name = param . name
kind = param . kind
ann = param . annotation
default = param . default
empty = Parameter . empty
if kind == Parameter . POSITIONAL_ONLY :
if args :
yield name , args . pop ( 0 ) , False
elif ann is not empty :
yield name , ann , False
else :
yield None , None , None
elif kind == Parameter . POSITIONAL_OR_KEYWORD :
if name in kwargs :
yield name , kwargs . pop ( name ) , True
elif args :
yield name , args . pop ( 0 ) , False
elif ann is not empty :
if default is empty :
yield name , ann , False
else :
yield name , ann , True
elif default is not empty :
yield name , default , True
else :
yield None , None , None
elif kind == Parameter . VAR_POSITIONAL :
# In this case name=args or something and we don't actually know the names.
for item in args [ : : ] :
args . pop ( 0 )
yield ' ' , item , False
elif kind == Parameter . KEYWORD_ONLY :
if name in kwargs :
yield name , kwargs . pop ( name ) , True
elif ann is not empty :
yield name , ann , True
elif default is not empty :
yield name , default , True
else :
yield None , None , None
elif kind == Parameter . VAR_KEYWORD :
# In this case name=kwargs and we yield the items in kwargs with their keys.
for k , v in kwargs . copy ( ) . items ( ) :
kwargs . pop ( k )
yield k , v , True
def interactive ( f , * * kwargs ) :
""" Build a group of widgets for setting the inputs to a function. """
def _find_abbreviations ( f , args , kwargs ) :
""" Find the abbreviations for a function and args/kwargs passed to interact. """
new_args = [ ]
new_kwargs = [ ]
for param in signature ( f ) . parameters . values ( ) :
for name , value , kw in _yield_abbreviations_for_parameter ( param , args , kwargs ) :
if value is None :
raise ValueError ( ' cannot find widget or abbreviation for argument: {!r} ' . format ( name ) )
if kw :
new_kwargs . append ( ( name , value ) )
else :
new_args . append ( ( name , value ) )
return new_args , new_kwargs
def _widgets_from_abbreviations ( seq ) :
""" Given a sequence of (name, abbrev) tuples, return a sequence of Widgets. """
result = [ ]
for name , abbrev in seq :
widget = _widget_from_abbrev ( abbrev )
widget . description = name
result . append ( widget )
return result
def interactive ( f , * args , * * kwargs ) :
""" Build a group of widgets to interact with a function. """
co = kwargs . pop ( ' clear_output ' , True )
# First convert all args to Widget instances
widgets = [ ]
args_widgets = [ ]
kwargs_ widgets = [ ]
container = ContainerWidget ( )
container . result = None
container . args = [ ]
container . kwargs = dict ( )
# 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 )
# We need this to be a list as we iteratively pop elements off it
args = list ( args )
kwargs = kwargs . copy ( )
new_args , new_kwargs = _find_abbreviations ( f , args , kwargs )
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
# that will lead to a valid call of the function. This protects against unspecified
# and doubly-specified arguments.
getcallargs ( f , * [ v for n , v in new_args ] , * * { n : v for n , v in new_kwargs } )
# Now build the widgets from the abbreviations.
args_widgets . extend ( _widgets_from_abbreviations ( new_args ) )
kwargs_widgets . extend ( _widgets_from_abbreviations ( new_kwargs ) )
kwargs_widgets . extend ( _widgets_from_abbreviations ( sorted ( kwargs . items ( ) , key = lambda x : x [ 0 ] ) ) )
# This has to be done as an assignment, not using container.children.append,
# so that traitlets notices the update.
container . children = widgets
container . children = args_widgets + kwargs_ widgets
# Build the callback
def call_f ( name , old , new ) :
actual_kwargs = { }
for widget in widgets :
container . args = [ ]
for widget in args_widgets :
value = widget . value
container . args . append ( value )
for widget in kwargs_widgets :
value = widget . value
container . kwargs [ widget . description ] = value
actual_kwargs [ widget . description ] = value
if co :
clear_output ( wait = True )
container . result = f ( * * actual_ kwargs)
container . result = f ( * container . args , * * container . kwargs)
# Wire up the widgets
for widget in widgets :
for widget in args_widgets :
widget . on_trait_change ( call_f , ' value ' )
for widget in kwargs_widgets :
widget . on_trait_change ( call_f , ' value ' )
container . on_displayed ( lambda _ : call_f ( None , None , None ) )
return container
def interact ( f , * * kwargs ) :
def interact ( f , * args , * * kwargs ) :
""" Interact with a function using widgets. """
w = interactive ( f , * * kwargs )
w = interactive ( f , * args , * * kwargs )
f . widget = w
display ( w )
def annotate ( * * kwargs ) :
""" Python 3 compatible function annotation for Python 2. """
if not kwargs :
raise ValueError ( ' annotations must be provided as keyword arguments ' )
def dec ( f ) :
if hasattr ( f , ' __annotations__ ' ) :
for k , v in kwargs . items ( ) :
f . __annotations__ [ k ] = v
else :
f . __annotations__ = kwargs
return f
return dec