|
@@
-1,5
+1,4
b''
|
|
1
|
"""Interact with functions using widgets.
|
|
1
|
"""Interact with functions using widgets."""
|
|
2
|
"""
|
|
|
|
|
3
|
|
|
2
|
|
|
4
|
#-----------------------------------------------------------------------------
|
|
3
|
#-----------------------------------------------------------------------------
|
|
5
|
# Copyright (c) 2013, the IPython Development Team.
|
|
4
|
# Copyright (c) 2013, the IPython Development Team.
|
|
@@
-13,10
+12,13
b''
|
|
13
|
# Imports
|
|
12
|
# Imports
|
|
14
|
#-----------------------------------------------------------------------------
|
|
13
|
#-----------------------------------------------------------------------------
|
|
15
|
|
|
14
|
|
|
|
|
|
15
|
from __future__ import print_function
|
|
|
|
|
16
|
|
|
16
|
try: # Python >= 3.3
|
|
17
|
try: # Python >= 3.3
|
|
17
|
from inspect import signature, Parameter
|
|
18
|
from inspect import signature, Parameter
|
|
18
|
except ImportError:
|
|
19
|
except ImportError:
|
|
19
|
from IPython.utils.signatures import signature, Parameter
|
|
20
|
from IPython.utils.signatures import signature, Parameter
|
|
|
|
|
21
|
from inspect import getcallargs
|
|
20
|
|
|
22
|
|
|
21
|
from IPython.html.widgets import (Widget, TextWidget,
|
|
23
|
from IPython.html.widgets import (Widget, TextWidget,
|
|
22
|
FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
|
|
24
|
FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
|
|
@@
-30,6
+32,7
b' from IPython.utils.py3compat import string_types, unicode_type'
|
|
30
|
|
|
32
|
|
|
31
|
|
|
33
|
|
|
32
|
def _matches(o, pattern):
|
|
34
|
def _matches(o, pattern):
|
|
|
|
|
35
|
"""Match a pattern of types in a sequence."""
|
|
33
|
if not len(o) == len(pattern):
|
|
36
|
if not len(o) == len(pattern):
|
|
34
|
return False
|
|
37
|
return False
|
|
35
|
comps = zip(o,pattern)
|
|
38
|
comps = zip(o,pattern)
|
|
@@
-49,9
+52,9
b' def _get_min_max_value(min, max, value):'
|
|
49
|
elif value == 0:
|
|
52
|
elif value == 0:
|
|
50
|
min, max, value = 0, 1, 0
|
|
53
|
min, max, value = 0, 1, 0
|
|
51
|
elif isinstance(value, float):
|
|
54
|
elif isinstance(value, float):
|
|
52
|
min, max = -value, 3.0*value
|
|
55
|
min, max = (-value, 3.0*value) if value > 0 else (3.0*value, -value)
|
|
53
|
elif isinstance(value, int):
|
|
56
|
elif isinstance(value, int):
|
|
54
|
min, max = -value, 3*value
|
|
57
|
min, max = (-value, 3*value) if value > 0 else (3*value, -value)
|
|
55
|
else:
|
|
58
|
else:
|
|
56
|
raise TypeError('expected a number, got: %r' % value)
|
|
59
|
raise TypeError('expected a number, got: %r' % value)
|
|
57
|
else:
|
|
60
|
else:
|
|
@@
-67,8
+70,6
b' def _widget_abbrev_single_value(o):'
|
|
67
|
values = o.values()
|
|
70
|
values = o.values()
|
|
68
|
w = DropdownWidget(value=values[0], values=values, labels=labels)
|
|
71
|
w = DropdownWidget(value=values[0], values=values, labels=labels)
|
|
69
|
return w
|
|
72
|
return w
|
|
70
|
# Special case float and int == 0.0
|
|
|
|
|
71
|
# get_range(value):
|
|
|
|
|
72
|
elif isinstance(o, bool):
|
|
73
|
elif isinstance(o, bool):
|
|
73
|
return CheckboxWidget(value=o)
|
|
74
|
return CheckboxWidget(value=o)
|
|
74
|
elif isinstance(o, float):
|
|
75
|
elif isinstance(o, float):
|
|
@@
-77,6
+78,8
b' def _widget_abbrev_single_value(o):'
|
|
77
|
elif isinstance(o, int):
|
|
78
|
elif isinstance(o, int):
|
|
78
|
min, max, value = _get_min_max_value(None, None, o)
|
|
79
|
min, max, value = _get_min_max_value(None, None, o)
|
|
79
|
return IntSliderWidget(value=o, min=min, max=max)
|
|
80
|
return IntSliderWidget(value=o, min=min, max=max)
|
|
|
|
|
81
|
else:
|
|
|
|
|
82
|
return None
|
|
80
|
|
|
83
|
|
|
81
|
def _widget_abbrev(o):
|
|
84
|
def _widget_abbrev(o):
|
|
82
|
"""Make widgets from abbreviations: single values, lists or tuples."""
|
|
85
|
"""Make widgets from abbreviations: single values, lists or tuples."""
|
|
@@
-99,92
+102,157
b' def _widget_abbrev(o):'
|
|
99
|
elif all(isinstance(x, string_types) for x in o):
|
|
102
|
elif all(isinstance(x, string_types) for x in o):
|
|
100
|
return DropdownWidget(value=unicode_type(o[0]),
|
|
103
|
return DropdownWidget(value=unicode_type(o[0]),
|
|
101
|
values=[unicode_type(k) for k in o])
|
|
104
|
values=[unicode_type(k) for k in o])
|
|
102
|
|
|
|
|
|
103
|
else:
|
|
105
|
else:
|
|
104
|
return _widget_abbrev_single_value(o)
|
|
106
|
return _widget_abbrev_single_value(o)
|
|
105
|
|
|
107
|
|
|
106
|
def _widget_or_abbrev(value):
|
|
108
|
def _widget_from_abbrev(abbrev):
|
|
107
|
if isinstance(value, Widget):
|
|
109
|
"""Build a Widget intstance given an abbreviation or Widget."""
|
|
108
|
return value
|
|
110
|
if isinstance(abbrev, Widget):
|
|
|
|
|
111
|
return abbrev
|
|
109
|
|
|
112
|
|
|
110
|
widget = _widget_abbrev(value)
|
|
113
|
widget = _widget_abbrev(abbrev)
|
|
111
|
if widget is None:
|
|
114
|
if widget is None:
|
|
112
|
raise ValueError("%r cannot be transformed to a Widget" % value)
|
|
115
|
raise ValueError("%r cannot be transformed to a Widget" % abbrev)
|
|
113
|
return widget
|
|
116
|
return widget
|
|
114
|
|
|
117
|
|
|
115
|
def _widget_for_param(param, kwargs):
|
|
118
|
def _yield_abbreviations_for_parameter(param, args, kwargs):
|
|
116
|
"""Get a widget for a parameter.
|
|
119
|
"""Get an abbreviation for a function parameter."""
|
|
117
|
|
|
120
|
# print(param, args, kwargs)
|
|
118
|
We look for, in this order:
|
|
121
|
name = param.name
|
|
119
|
- keyword arguments passed to interact[ive]() that match the parameter name.
|
|
122
|
kind = param.kind
|
|
120
|
- function annotations
|
|
123
|
ann = param.annotation
|
|
121
|
- default values
|
|
124
|
default = param.default
|
|
122
|
|
|
125
|
empty = Parameter.empty
|
|
123
|
Returns an instance of Widget, or None if nothing suitable is found.
|
|
126
|
if kind == Parameter.POSITIONAL_ONLY:
|
|
124
|
|
|
127
|
if args:
|
|
125
|
Raises ValueError if the kwargs or annotation value cannot be made into
|
|
128
|
yield name, args.pop(0), False
|
|
126
|
a widget.
|
|
129
|
elif ann is not empty:
|
|
127
|
"""
|
|
130
|
yield name, ann, False
|
|
128
|
if param.name in kwargs:
|
|
131
|
else:
|
|
129
|
return _widget_or_abbrev(kwargs.pop(param.name))
|
|
132
|
yield None, None, None
|
|
130
|
|
|
133
|
elif kind == Parameter.POSITIONAL_OR_KEYWORD:
|
|
131
|
if param.annotation is not Parameter.empty:
|
|
134
|
if name in kwargs:
|
|
132
|
return _widget_or_abbrev(param.annotation)
|
|
135
|
yield name, kwargs.pop(name), True
|
|
133
|
|
|
136
|
elif args:
|
|
134
|
if param.default is not Parameter.empty:
|
|
137
|
yield name, args.pop(0), False
|
|
135
|
# Returns None if it's not suitable
|
|
138
|
elif ann is not empty:
|
|
136
|
return _widget_abbrev_single_value(param.default)
|
|
139
|
if default is empty:
|
|
137
|
|
|
140
|
yield name, ann, False
|
|
138
|
return None
|
|
141
|
else:
|
|
|
|
|
142
|
yield name, ann, True
|
|
|
|
|
143
|
elif default is not empty:
|
|
|
|
|
144
|
yield name, default, True
|
|
|
|
|
145
|
else:
|
|
|
|
|
146
|
yield None, None, None
|
|
|
|
|
147
|
elif kind == Parameter.VAR_POSITIONAL:
|
|
|
|
|
148
|
# In this case name=args or something and we don't actually know the names.
|
|
|
|
|
149
|
for item in args[::]:
|
|
|
|
|
150
|
args.pop(0)
|
|
|
|
|
151
|
yield '', item, False
|
|
|
|
|
152
|
elif kind == Parameter.KEYWORD_ONLY:
|
|
|
|
|
153
|
if name in kwargs:
|
|
|
|
|
154
|
yield name, kwargs.pop(name), True
|
|
|
|
|
155
|
elif ann is not empty:
|
|
|
|
|
156
|
yield name, ann, True
|
|
|
|
|
157
|
elif default is not empty:
|
|
|
|
|
158
|
yield name, default, True
|
|
|
|
|
159
|
else:
|
|
|
|
|
160
|
yield None, None, None
|
|
|
|
|
161
|
elif kind == Parameter.VAR_KEYWORD:
|
|
|
|
|
162
|
# In this case name=kwargs and we yield the items in kwargs with their keys.
|
|
|
|
|
163
|
for k, v in kwargs.copy().items():
|
|
|
|
|
164
|
kwargs.pop(k)
|
|
|
|
|
165
|
yield k, v, True
|
|
139
|
|
|
166
|
|
|
140
|
def interactive(f, **kwargs):
|
|
167
|
def _find_abbreviations(f, args, kwargs):
|
|
141
|
"""Build a group of widgets for setting the inputs to a function."""
|
|
168
|
"""Find the abbreviations for a function and args/kwargs passed to interact."""
|
|
142
|
|
|
169
|
new_args = []
|
|
|
|
|
170
|
new_kwargs = []
|
|
|
|
|
171
|
for param in signature(f).parameters.values():
|
|
|
|
|
172
|
for name, value, kw in _yield_abbreviations_for_parameter(param, args, kwargs):
|
|
|
|
|
173
|
if value is None:
|
|
|
|
|
174
|
raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
|
|
|
|
|
175
|
if kw:
|
|
|
|
|
176
|
new_kwargs.append((name, value))
|
|
|
|
|
177
|
else:
|
|
|
|
|
178
|
new_args.append((name, value))
|
|
|
|
|
179
|
return new_args, new_kwargs
|
|
|
|
|
180
|
|
|
|
|
|
181
|
def _widgets_from_abbreviations(seq):
|
|
|
|
|
182
|
"""Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
|
|
|
|
|
183
|
result = []
|
|
|
|
|
184
|
for name, abbrev in seq:
|
|
|
|
|
185
|
widget = _widget_from_abbrev(abbrev)
|
|
|
|
|
186
|
widget.description = name
|
|
|
|
|
187
|
result.append(widget)
|
|
|
|
|
188
|
return result
|
|
|
|
|
189
|
|
|
|
|
|
190
|
def interactive(f, *args, **kwargs):
|
|
|
|
|
191
|
"""Build a group of widgets to interact with a function."""
|
|
143
|
co = kwargs.pop('clear_output', True)
|
|
192
|
co = kwargs.pop('clear_output', True)
|
|
144
|
# First convert all args to Widget instances
|
|
193
|
args_widgets = []
|
|
145
|
widgets = []
|
|
194
|
kwargs_widgets = []
|
|
146
|
container = ContainerWidget()
|
|
195
|
container = ContainerWidget()
|
|
147
|
container.result = None
|
|
196
|
container.result = None
|
|
|
|
|
197
|
container.args = []
|
|
148
|
container.kwargs = dict()
|
|
198
|
container.kwargs = dict()
|
|
149
|
|
|
199
|
# We need this to be a list as we iteratively pop elements off it
|
|
150
|
# Extract parameters from the function signature
|
|
200
|
args = list(args)
|
|
151
|
for param in signature(f).parameters.values():
|
|
201
|
kwargs = kwargs.copy()
|
|
152
|
param_widget = _widget_for_param(param, kwargs)
|
|
202
|
|
|
153
|
if param_widget is not None:
|
|
203
|
new_args, new_kwargs = _find_abbreviations(f, args, kwargs)
|
|
154
|
param_widget.description = param.name
|
|
204
|
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
|
|
155
|
widgets.append(param_widget)
|
|
205
|
# that will lead to a valid call of the function. This protects against unspecified
|
|
156
|
|
|
206
|
# and doubly-specified arguments.
|
|
157
|
# Extra parameters from keyword args - we assume f takes **kwargs
|
|
207
|
getcallargs(f, *[v for n,v in new_args], **{n:v for n,v in new_kwargs})
|
|
158
|
for name, value in sorted(kwargs.items(), key = lambda x: x[0]):
|
|
208
|
# Now build the widgets from the abbreviations.
|
|
159
|
widget = _widget_or_abbrev(value)
|
|
209
|
args_widgets.extend(_widgets_from_abbreviations(new_args))
|
|
160
|
widget.description = name
|
|
210
|
kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
|
|
161
|
widgets.append(widget)
|
|
211
|
kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
|
|
162
|
|
|
212
|
|
|
163
|
# This has to be done as an assignment, not using container.children.append,
|
|
213
|
# This has to be done as an assignment, not using container.children.append,
|
|
164
|
# so that traitlets notices the update.
|
|
214
|
# so that traitlets notices the update.
|
|
165
|
container.children = widgets
|
|
215
|
container.children = args_widgets + kwargs_widgets
|
|
166
|
|
|
216
|
|
|
167
|
# Build the callback
|
|
217
|
# Build the callback
|
|
168
|
def call_f(name, old, new):
|
|
218
|
def call_f(name, old, new):
|
|
169
|
actual_kwargs = {}
|
|
219
|
container.args = []
|
|
170
|
for widget in widgets:
|
|
220
|
for widget in args_widgets:
|
|
|
|
|
221
|
value = widget.value
|
|
|
|
|
222
|
container.args.append(value)
|
|
|
|
|
223
|
for widget in kwargs_widgets:
|
|
171
|
value = widget.value
|
|
224
|
value = widget.value
|
|
172
|
container.kwargs[widget.description] = value
|
|
225
|
container.kwargs[widget.description] = value
|
|
173
|
actual_kwargs[widget.description] = value
|
|
|
|
|
174
|
if co:
|
|
226
|
if co:
|
|
175
|
clear_output(wait=True)
|
|
227
|
clear_output(wait=True)
|
|
176
|
container.result = f(**actual_kwargs)
|
|
228
|
container.result = f(*container.args, **container.kwargs)
|
|
177
|
|
|
229
|
|
|
178
|
# Wire up the widgets
|
|
230
|
# Wire up the widgets
|
|
179
|
for widget in widgets:
|
|
231
|
for widget in args_widgets:
|
|
|
|
|
232
|
widget.on_trait_change(call_f, 'value')
|
|
|
|
|
233
|
for widget in kwargs_widgets:
|
|
180
|
widget.on_trait_change(call_f, 'value')
|
|
234
|
widget.on_trait_change(call_f, 'value')
|
|
181
|
|
|
235
|
|
|
182
|
container.on_displayed(lambda _: call_f(None, None, None))
|
|
236
|
container.on_displayed(lambda _: call_f(None, None, None))
|
|
183
|
|
|
237
|
|
|
184
|
return container
|
|
238
|
return container
|
|
185
|
|
|
239
|
|
|
186
|
def interact(f, **kwargs):
|
|
240
|
def interact(f, *args, **kwargs):
|
|
187
|
"""Interact with a function using widgets."""
|
|
241
|
"""Interact with a function using widgets."""
|
|
188
|
w = interactive(f, **kwargs)
|
|
242
|
w = interactive(f, *args, **kwargs)
|
|
189
|
f.widget = w
|
|
243
|
f.widget = w
|
|
190
|
display(w)
|
|
244
|
display(w)
|
|
|
|
|
245
|
|
|
|
|
|
246
|
def annotate(**kwargs):
|
|
|
|
|
247
|
"""Python 3 compatible function annotation for Python 2."""
|
|
|
|
|
248
|
if not kwargs:
|
|
|
|
|
249
|
raise ValueError('annotations must be provided as keyword arguments')
|
|
|
|
|
250
|
def dec(f):
|
|
|
|
|
251
|
if hasattr(f, '__annotations__'):
|
|
|
|
|
252
|
for k, v in kwargs.items():
|
|
|
|
|
253
|
f.__annotations__[k] = v
|
|
|
|
|
254
|
else:
|
|
|
|
|
255
|
f.__annotations__ = kwargs
|
|
|
|
|
256
|
return f
|
|
|
|
|
257
|
return dec
|
|
|
|
|
258
|
|