1
|
|
"""
|
2
|
|
Interact with functions using widgets.
|
3
|
|
|
4
|
|
The interact Pane implemented in this module mirrors
|
5
|
|
ipywidgets.interact in its API and implementation. Large parts of the
|
6
|
|
code were copied directly from ipywidgets:
|
7
|
|
|
8
|
|
Copyright (c) Jupyter Development Team and PyViz Development Team.
|
9
|
|
Distributed under the terms of the Modified BSD License.
|
10
|
|
"""
|
11
|
7
|
import types
|
12
|
|
|
13
|
7
|
from collections import OrderedDict
|
14
|
7
|
from inspect import getcallargs
|
15
|
7
|
from numbers import Real, Integral
|
16
|
7
|
from six import string_types
|
17
|
|
|
18
|
7
|
try: # Python >= 3.3
|
19
|
7
|
from inspect import signature, Parameter
|
20
|
7
|
from collections.abc import Iterable, Mapping
|
21
|
7
|
empty = Parameter.empty
|
22
|
0
|
except ImportError:
|
23
|
0
|
from collections import Iterable, Mapping
|
24
|
0
|
try:
|
25
|
0
|
from IPython.utils.signatures import signature, Parameter
|
26
|
0
|
empty = Parameter.empty
|
27
|
0
|
except Exception:
|
28
|
0
|
signature, Parameter, empty = None, None, None
|
29
|
|
|
30
|
7
|
try:
|
31
|
7
|
from inspect import getfullargspec as check_argspec
|
32
|
0
|
except ImportError:
|
33
|
0
|
from inspect import getargspec as check_argspec # py2
|
34
|
|
|
35
|
7
|
import param
|
36
|
|
|
37
|
7
|
from .layout import Panel, Column, Row
|
38
|
7
|
from .pane import PaneBase, HTML, panel
|
39
|
7
|
from .pane.base import ReplacementPane
|
40
|
7
|
from .util import as_unicode
|
41
|
7
|
from .viewable import Viewable
|
42
|
7
|
from .widgets import (Checkbox, TextInput, Widget, IntSlider, FloatSlider,
|
43
|
|
Select, DiscreteSlider, Button)
|
44
|
|
|
45
|
|
|
46
|
7
|
def _get_min_max_value(min, max, value=None, step=None):
|
47
|
|
"""Return min, max, value given input values with possible None."""
|
48
|
|
# Either min and max need to be given, or value needs to be given
|
49
|
7
|
if value is None:
|
50
|
7
|
if min is None or max is None:
|
51
|
0
|
raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
|
52
|
7
|
diff = max - min
|
53
|
7
|
value = min + (diff / 2)
|
54
|
|
# Ensure that value has the same type as diff
|
55
|
7
|
if not isinstance(value, type(diff)):
|
56
|
7
|
value = min + (diff // 2)
|
57
|
|
else: # value is not None
|
58
|
7
|
if not isinstance(value, Real):
|
59
|
0
|
raise TypeError('expected a real number, got: %r' % value)
|
60
|
|
# Infer min/max from value
|
61
|
7
|
if value == 0:
|
62
|
|
# This gives (0, 1) of the correct type
|
63
|
7
|
vrange = (value, value + 1)
|
64
|
7
|
elif value > 0:
|
65
|
7
|
vrange = (-value, 3*value)
|
66
|
|
else:
|
67
|
0
|
vrange = (3*value, -value)
|
68
|
7
|
if min is None:
|
69
|
7
|
min = vrange[0]
|
70
|
7
|
if max is None:
|
71
|
7
|
max = vrange[1]
|
72
|
7
|
if step is not None:
|
73
|
|
# ensure value is on a step
|
74
|
7
|
tick = int((value - min) / step)
|
75
|
7
|
value = min + tick * step
|
76
|
7
|
if not min <= value <= max:
|
77
|
0
|
raise ValueError('value must be between min and max (min={0}, value={1}, max={2})'.format(min, value, max))
|
78
|
7
|
return min, max, value
|
79
|
|
|
80
|
|
|
81
|
7
|
def _yield_abbreviations_for_parameter(parameter, kwargs):
|
82
|
|
"""Get an abbreviation for a function parameter."""
|
83
|
7
|
name = parameter.name
|
84
|
7
|
kind = parameter.kind
|
85
|
7
|
ann = parameter.annotation
|
86
|
7
|
default = parameter.default
|
87
|
7
|
not_found = (name, empty, empty)
|
88
|
7
|
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
|
89
|
7
|
if name in kwargs:
|
90
|
7
|
value = kwargs.pop(name)
|
91
|
0
|
elif ann is not empty:
|
92
|
0
|
param.main.warning("Using function annotations to implicitly specify interactive controls is deprecated. "
|
93
|
|
"Use an explicit keyword argument for the parameter instead.", DeprecationWarning)
|
94
|
0
|
value = ann
|
95
|
0
|
elif default is not empty:
|
96
|
0
|
value = default
|
97
|
0
|
if isinstance(value, (Iterable, Mapping)):
|
98
|
0
|
value = fixed(value)
|
99
|
|
else:
|
100
|
0
|
yield not_found
|
101
|
7
|
yield (name, value, default)
|
102
|
0
|
elif kind == Parameter.VAR_KEYWORD:
|
103
|
|
# In this case name=kwargs and we yield the items in kwargs with their keys.
|
104
|
0
|
for k, v in kwargs.copy().items():
|
105
|
0
|
kwargs.pop(k)
|
106
|
0
|
yield k, v, empty
|
107
|
|
|
108
|
|
|
109
|
7
|
def _matches(o, pattern):
|
110
|
|
"""Match a pattern of types in a sequence."""
|
111
|
7
|
if not len(o) == len(pattern):
|
112
|
7
|
return False
|
113
|
7
|
comps = zip(o,pattern)
|
114
|
7
|
return all(isinstance(obj,kind) for obj,kind in comps)
|
115
|
|
|
116
|
|
|
117
|
7
|
class interactive(PaneBase):
|
118
|
|
|
119
|
7
|
default_layout = param.ClassSelector(default=Column, class_=(Panel),
|
120
|
|
is_instance=False)
|
121
|
|
|
122
|
7
|
manual_update = param.Boolean(default=False, doc="""
|
123
|
|
Whether to update manually by clicking on button.""")
|
124
|
|
|
125
|
7
|
manual_name = param.String(default='Run Interact')
|
126
|
|
|
127
|
7
|
def __init__(self, object, params={}, **kwargs):
|
128
|
7
|
if signature is None:
|
129
|
0
|
raise ImportError('interact requires either recent Python version '
|
130
|
|
'(>=3.3 or IPython to inspect function signatures.')
|
131
|
|
|
132
|
7
|
super().__init__(object, **params)
|
133
|
|
|
134
|
7
|
self.throttled = kwargs.pop('throttled', False)
|
135
|
7
|
new_kwargs = self.find_abbreviations(kwargs)
|
136
|
|
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
|
137
|
|
# that will lead to a valid call of the function. This protects against unspecified
|
138
|
|
# and doubly-specified arguments.
|
139
|
7
|
try:
|
140
|
7
|
check_argspec(object)
|
141
|
0
|
except TypeError:
|
142
|
|
# if we can't inspect, we can't validate
|
143
|
0
|
pass
|
144
|
|
else:
|
145
|
7
|
getcallargs(object, **{n:v for n,v,_ in new_kwargs})
|
146
|
|
|
147
|
7
|
widgets = self.widgets_from_abbreviations(new_kwargs)
|
148
|
7
|
if self.manual_update:
|
149
|
7
|
widgets.append(('manual', Button(name=self.manual_name)))
|
150
|
7
|
self._widgets = OrderedDict(widgets)
|
151
|
7
|
pane = self.object(**self.kwargs)
|
152
|
7
|
if isinstance(pane, Viewable):
|
153
|
0
|
self._pane = pane
|
154
|
0
|
self._internal = False
|
155
|
|
else:
|
156
|
7
|
self._pane = panel(pane, name=self.name)
|
157
|
7
|
self._internal = True
|
158
|
7
|
self._inner_layout = Row(self._pane)
|
159
|
7
|
widgets = [widget for _, widget in widgets if isinstance(widget, Widget)]
|
160
|
7
|
if 'name' in params:
|
161
|
7
|
widgets.insert(0, HTML('<h2>%s</h2>' % self.name))
|
162
|
7
|
self.widget_box = Column(*widgets)
|
163
|
7
|
self.layout.objects = [self.widget_box, self._inner_layout]
|
164
|
7
|
self._link_widgets()
|
165
|
|
|
166
|
|
#----------------------------------------------------------------
|
167
|
|
# Model API
|
168
|
|
#----------------------------------------------------------------
|
169
|
|
|
170
|
7
|
def _get_model(self, doc, root=None, parent=None, comm=None):
|
171
|
0
|
return self._inner_layout._get_model(doc, root, parent, comm)
|
172
|
|
|
173
|
|
#----------------------------------------------------------------
|
174
|
|
# Callback API
|
175
|
|
#----------------------------------------------------------------
|
176
|
|
|
177
|
7
|
@property
|
178
|
2
|
def _synced_params(self):
|
179
|
7
|
return []
|
180
|
|
|
181
|
7
|
def _link_widgets(self):
|
182
|
7
|
if self.manual_update:
|
183
|
7
|
widgets = [('manual', self._widgets['manual'])]
|
184
|
|
else:
|
185
|
7
|
widgets = self._widgets.items()
|
186
|
|
|
187
|
7
|
for name, widget in widgets:
|
188
|
7
|
def update_pane(change):
|
189
|
|
# Try updating existing pane
|
190
|
7
|
new_object = self.object(**self.kwargs)
|
191
|
7
|
new_pane, internal = ReplacementPane._update_from_object(
|
192
|
|
new_object, self._pane, self._internal
|
193
|
|
)
|
194
|
7
|
if new_pane is None:
|
195
|
7
|
return
|
196
|
|
|
197
|
|
# Replace pane entirely
|
198
|
7
|
self._pane = new_pane
|
199
|
7
|
self._inner_layout[0] = new_pane
|
200
|
7
|
self._internal = internal
|
201
|
|
|
202
|
7
|
if self.throttled and hasattr(widget, 'value_throttled'):
|
203
|
7
|
v = 'value_throttled'
|
204
|
|
else:
|
205
|
7
|
v = 'value'
|
206
|
|
|
207
|
7
|
pname = 'clicks' if name == 'manual' else v
|
208
|
7
|
watcher = widget.param.watch(update_pane, pname)
|
209
|
7
|
self._callbacks.append(watcher)
|
210
|
|
|
211
|
7
|
def _cleanup(self, root):
|
212
|
7
|
self._inner_layout._cleanup(root)
|
213
|
7
|
super()._cleanup(root)
|
214
|
|
|
215
|
|
#----------------------------------------------------------------
|
216
|
|
# Public API
|
217
|
|
#----------------------------------------------------------------
|
218
|
|
|
219
|
7
|
@property
|
220
|
2
|
def kwargs(self):
|
221
|
7
|
return {k: widget.value for k, widget in self._widgets.items()
|
222
|
|
if k != 'manual'}
|
223
|
|
|
224
|
7
|
def signature(self):
|
225
|
7
|
return signature(self.object)
|
226
|
|
|
227
|
7
|
def find_abbreviations(self, kwargs):
|
228
|
|
"""Find the abbreviations for the given function and kwargs.
|
229
|
|
Return (name, abbrev, default) tuples.
|
230
|
|
"""
|
231
|
7
|
new_kwargs = []
|
232
|
7
|
try:
|
233
|
7
|
sig = self.signature()
|
234
|
0
|
except (ValueError, TypeError):
|
235
|
|
# can't inspect, no info from function; only use kwargs
|
236
|
0
|
return [ (key, value, value) for key, value in kwargs.items() ]
|
237
|
|
|
238
|
7
|
for parameter in sig.parameters.values():
|
239
|
7
|
for name, value, default in _yield_abbreviations_for_parameter(parameter, kwargs):
|
240
|
7
|
if value is empty:
|
241
|
0
|
raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
|
242
|
7
|
new_kwargs.append((name, value, default))
|
243
|
7
|
return new_kwargs
|
244
|
|
|
245
|
7
|
def widgets_from_abbreviations(self, seq):
|
246
|
|
"""Given a sequence of (name, abbrev, default) tuples, return a sequence of Widgets."""
|
247
|
7
|
result = []
|
248
|
7
|
for name, abbrev, default in seq:
|
249
|
7
|
if isinstance(abbrev, fixed):
|
250
|
0
|
widget = abbrev
|
251
|
|
else:
|
252
|
7
|
widget = self.widget_from_abbrev(abbrev, name, default)
|
253
|
7
|
if not (isinstance(widget, Widget) or isinstance(widget, fixed)):
|
254
|
0
|
if widget is None:
|
255
|
0
|
continue
|
256
|
|
else:
|
257
|
0
|
raise TypeError("{!r} is not a ValueWidget".format(widget))
|
258
|
7
|
result.append((name, widget))
|
259
|
7
|
return result
|
260
|
|
|
261
|
7
|
@classmethod
|
262
|
2
|
def applies(cls, object):
|
263
|
7
|
return isinstance(object, types.FunctionType)
|
264
|
|
|
265
|
7
|
@classmethod
|
266
|
7
|
def widget_from_abbrev(cls, abbrev, name, default=empty):
|
267
|
|
"""Build a ValueWidget instance given an abbreviation or Widget."""
|
268
|
7
|
if isinstance(abbrev, Widget):
|
269
|
7
|
return abbrev
|
270
|
|
|
271
|
7
|
if isinstance(abbrev, tuple):
|
272
|
7
|
widget = cls.widget_from_tuple(abbrev, name, default)
|
273
|
7
|
if default is not empty:
|
274
|
7
|
try:
|
275
|
7
|
widget.value = default
|
276
|
0
|
except Exception:
|
277
|
|
# ignore failure to set default
|
278
|
0
|
pass
|
279
|
7
|
return widget
|
280
|
|
|
281
|
|
# Try single value
|
282
|
7
|
widget = cls.widget_from_single_value(abbrev, name)
|
283
|
7
|
if widget is not None:
|
284
|
7
|
return widget
|
285
|
|
|
286
|
|
# Something iterable (list, dict, generator, ...). Note that str and
|
287
|
|
# tuple should be handled before, that is why we check this case last.
|
288
|
7
|
if isinstance(abbrev, Iterable):
|
289
|
7
|
widget = cls.widget_from_iterable(abbrev, name)
|
290
|
7
|
if default is not empty:
|
291
|
0
|
try:
|
292
|
0
|
widget.value = default
|
293
|
0
|
except Exception:
|
294
|
|
# ignore failure to set default
|
295
|
0
|
pass
|
296
|
7
|
return widget
|
297
|
|
|
298
|
|
# No idea...
|
299
|
0
|
return fixed(abbrev)
|
300
|
|
|
301
|
7
|
@staticmethod
|
302
|
2
|
def widget_from_single_value(o, name):
|
303
|
|
"""Make widgets from single values, which can be used as parameter defaults."""
|
304
|
7
|
if isinstance(o, string_types):
|
305
|
7
|
return TextInput(value=as_unicode(o), name=name)
|
306
|
7
|
elif isinstance(o, bool):
|
307
|
7
|
return Checkbox(value=o, name=name)
|
308
|
7
|
elif isinstance(o, Integral):
|
309
|
7
|
min, max, value = _get_min_max_value(None, None, o)
|
310
|
7
|
return IntSlider(value=o, start=min, end=max, name=name)
|
311
|
7
|
elif isinstance(o, Real):
|
312
|
0
|
min, max, value = _get_min_max_value(None, None, o)
|
313
|
0
|
return FloatSlider(value=o, start=min, end=max, name=name)
|
314
|
|
else:
|
315
|
7
|
return None
|
316
|
|
|
317
|
7
|
@staticmethod
|
318
|
7
|
def widget_from_tuple(o, name, default=empty):
|
319
|
|
"""Make widgets from a tuple abbreviation."""
|
320
|
7
|
int_default = (default is empty or isinstance(default, int))
|
321
|
7
|
if _matches(o, (Real, Real)):
|
322
|
7
|
min, max, value = _get_min_max_value(o[0], o[1])
|
323
|
7
|
if all(isinstance(_, Integral) for _ in o) and int_default:
|
324
|
7
|
cls = IntSlider
|
325
|
|
else:
|
326
|
7
|
cls = FloatSlider
|
327
|
7
|
return cls(value=value, start=min, end=max, name=name)
|
328
|
7
|
elif _matches(o, (Real, Real, Real)):
|
329
|
7
|
step = o[2]
|
330
|
7
|
if step <= 0:
|
331
|
0
|
raise ValueError("step must be >= 0, not %r" % step)
|
332
|
7
|
min, max, value = _get_min_max_value(o[0], o[1], step=step)
|
333
|
7
|
if all(isinstance(_, Integral) for _ in o) and int_default:
|
334
|
7
|
cls = IntSlider
|
335
|
|
else:
|
336
|
7
|
cls = FloatSlider
|
337
|
7
|
return cls(value=value, start=min, end=max, step=step, name=name)
|
338
|
7
|
elif _matches(o, (Real, Real, Real, Real)):
|
339
|
7
|
step = o[2]
|
340
|
7
|
if step <= 0:
|
341
|
0
|
raise ValueError("step must be >= 0, not %r" % step)
|
342
|
7
|
min, max, value = _get_min_max_value(o[0], o[1], value=o[3], step=step)
|
343
|
7
|
if all(isinstance(_, Integral) for _ in o):
|
344
|
7
|
cls = IntSlider
|
345
|
|
else:
|
346
|
7
|
cls = FloatSlider
|
347
|
7
|
return cls(value=value, start=min, end=max, step=step, name=name)
|
348
|
7
|
elif len(o) == 4:
|
349
|
7
|
min, max, value = _get_min_max_value(o[0], o[1], value=o[3])
|
350
|
7
|
if all(isinstance(_, Integral) for _ in [o[0], o[1], o[3]]):
|
351
|
7
|
cls = IntSlider
|
352
|
|
else:
|
353
|
0
|
cls = FloatSlider
|
354
|
7
|
return cls(value=value, start=min, end=max, name=name)
|
355
|
|
|
356
|
7
|
@staticmethod
|
357
|
2
|
def widget_from_iterable(o, name):
|
358
|
|
"""Make widgets from an iterable. This should not be done for
|
359
|
|
a string or tuple."""
|
360
|
|
# Select expects a dict or list, so we convert an arbitrary
|
361
|
|
# iterable to either of those.
|
362
|
7
|
values = list(o.values()) if isinstance(o, Mapping) else list(o)
|
363
|
7
|
widget_type = DiscreteSlider if all(param._is_number(v) for v in values) else Select
|
364
|
7
|
if isinstance(o, (list, dict)):
|
365
|
7
|
return widget_type(options=o, name=name)
|
366
|
0
|
elif isinstance(o, Mapping):
|
367
|
0
|
return widget_type(options=list(o.items()), name=name)
|
368
|
|
else:
|
369
|
0
|
return widget_type(options=list(o), name=name)
|
370
|
|
|
371
|
|
# Return a factory for interactive functions
|
372
|
7
|
@classmethod
|
373
|
2
|
def factory(cls):
|
374
|
7
|
options = dict(manual_update=False, manual_name="Run Interact")
|
375
|
7
|
return _InteractFactory(cls, options)
|
376
|
|
|
377
|
|
|
378
|
7
|
class _InteractFactory(object):
|
379
|
|
"""
|
380
|
|
Factory for instances of :class:`interactive`.
|
381
|
|
|
382
|
|
Arguments
|
383
|
|
---------
|
384
|
|
cls: class
|
385
|
|
The subclass of :class:`interactive` to construct.
|
386
|
|
options: dict
|
387
|
|
A dict of options used to construct the interactive
|
388
|
|
function. By default, this is returned by
|
389
|
|
``cls.default_options()``.
|
390
|
|
kwargs: dict
|
391
|
|
A dict of **kwargs to use for widgets.
|
392
|
|
"""
|
393
|
7
|
def __init__(self, cls, options, kwargs=None):
|
394
|
7
|
self.cls = cls
|
395
|
7
|
self.opts = options
|
396
|
7
|
self.kwargs = kwargs or {}
|
397
|
|
|
398
|
7
|
def widget(self, f):
|
399
|
|
"""
|
400
|
|
Return an interactive function widget for the given function.
|
401
|
|
The widget is only constructed, not displayed nor attached to
|
402
|
|
the function.
|
403
|
|
Returns
|
404
|
|
-------
|
405
|
|
An instance of ``self.cls`` (typically :class:`interactive`).
|
406
|
|
Parameters
|
407
|
|
----------
|
408
|
|
f : function
|
409
|
|
The function to which the interactive widgets are tied.
|
410
|
|
"""
|
411
|
0
|
return self.cls(f, self.opts, **self.kwargs)
|
412
|
|
|
413
|
7
|
def __call__(self, __interact_f=None, **kwargs):
|
414
|
|
"""
|
415
|
|
Make the given function interactive by adding and displaying
|
416
|
|
the corresponding :class:`interactive` widget.
|
417
|
|
Expects the first argument to be a function. Parameters to this
|
418
|
|
function are widget abbreviations passed in as keyword arguments
|
419
|
|
(``**kwargs``). Can be used as a decorator (see examples).
|
420
|
|
Returns
|
421
|
|
-------
|
422
|
|
f : __interact_f with interactive widget attached to it.
|
423
|
|
Parameters
|
424
|
|
----------
|
425
|
|
__interact_f : function
|
426
|
|
The function to which the interactive widgets are tied. The `**kwargs`
|
427
|
|
should match the function signature. Passed to :func:`interactive()`
|
428
|
|
**kwargs : various, optional
|
429
|
|
An interactive widget is created for each keyword argument that is a
|
430
|
|
valid widget abbreviation. Passed to :func:`interactive()`
|
431
|
|
Examples
|
432
|
|
--------
|
433
|
|
Render an interactive text field that shows the greeting with the passed in
|
434
|
|
text::
|
435
|
|
# 1. Using interact as a function
|
436
|
|
def greeting(text="World"):
|
437
|
|
print("Hello {}".format(text))
|
438
|
|
interact(greeting, text="IPython Widgets")
|
439
|
|
# 2. Using interact as a decorator
|
440
|
|
@interact
|
441
|
|
def greeting(text="World"):
|
442
|
|
print("Hello {}".format(text))
|
443
|
|
# 3. Using interact as a decorator with named parameters
|
444
|
|
@interact(text="IPython Widgets")
|
445
|
|
def greeting(text="World"):
|
446
|
|
print("Hello {}".format(text))
|
447
|
|
Render an interactive slider widget and prints square of number::
|
448
|
|
# 1. Using interact as a function
|
449
|
|
def square(num=1):
|
450
|
|
print("{} squared is {}".format(num, num*num))
|
451
|
|
interact(square, num=5)
|
452
|
|
# 2. Using interact as a decorator
|
453
|
|
@interact
|
454
|
|
def square(num=2):
|
455
|
|
print("{} squared is {}".format(num, num*num))
|
456
|
|
# 3. Using interact as a decorator with named parameters
|
457
|
|
@interact(num=5)
|
458
|
|
def square(num=2):
|
459
|
|
print("{} squared is {}".format(num, num*num))
|
460
|
|
"""
|
461
|
|
# If kwargs are given, replace self by a new
|
462
|
|
# _InteractFactory with the updated kwargs
|
463
|
0
|
if kwargs:
|
464
|
0
|
params = list(interactive.param)
|
465
|
0
|
kw = dict(self.kwargs)
|
466
|
0
|
kw.update({k: v for k, v in kwargs.items() if k not in params})
|
467
|
0
|
opts = dict(self.opts, **{k: v for k, v in kwargs.items() if k in params})
|
468
|
0
|
self = type(self)(self.cls, opts, kw)
|
469
|
|
|
470
|
0
|
f = __interact_f
|
471
|
0
|
if f is None:
|
472
|
|
# This branch handles the case 3
|
473
|
|
# @interact(a=30, b=40)
|
474
|
|
# def f(*args, **kwargs):
|
475
|
|
# ...
|
476
|
|
#
|
477
|
|
# Simply return the new factory
|
478
|
0
|
return self
|
479
|
0
|
elif 'throttled' in check_argspec(f).args:
|
480
|
0
|
raise ValueError('A function cannot have "throttled" as an argument')
|
481
|
|
|
482
|
|
# positional arg support in: https://gist.github.com/8851331
|
483
|
|
# Handle the cases 1 and 2
|
484
|
|
# 1. interact(f, **kwargs)
|
485
|
|
# 2. @interact
|
486
|
|
# def f(*args, **kwargs):
|
487
|
|
# ...
|
488
|
0
|
w = self.widget(f)
|
489
|
0
|
try:
|
490
|
0
|
f.widget = w
|
491
|
0
|
except AttributeError:
|
492
|
|
# some things (instancemethods) can't have attributes attached,
|
493
|
|
# so wrap in a lambda
|
494
|
0
|
f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
|
495
|
0
|
f.widget = w
|
496
|
0
|
return w.layout
|
497
|
|
|
498
|
7
|
def options(self, **kwds):
|
499
|
|
"""
|
500
|
|
Change options for interactive functions.
|
501
|
|
Returns
|
502
|
|
-------
|
503
|
|
A new :class:`_InteractFactory` which will apply the
|
504
|
|
options when called.
|
505
|
|
"""
|
506
|
7
|
opts = dict(self.opts)
|
507
|
7
|
for k in kwds:
|
508
|
7
|
if k not in opts:
|
509
|
0
|
raise ValueError("invalid option {!r}".format(k))
|
510
|
7
|
opts[k] = kwds[k]
|
511
|
7
|
return type(self)(self.cls, opts, self.kwargs)
|
512
|
|
|
513
|
|
|
514
|
7
|
interact = interactive.factory()
|
515
|
7
|
interact_manual = interact.options(manual_update=True, manual_name="Run Interact")
|
516
|
|
|
517
|
|
|
518
|
7
|
class fixed(param.Parameterized):
|
519
|
|
"""A pseudo-widget whose value is fixed and never synced to the client."""
|
520
|
7
|
value = param.Parameter(doc="Any Python object")
|
521
|
7
|
description = param.String(default='')
|
522
|
|
|
523
|
7
|
def __init__(self, value, **kwargs):
|
524
|
0
|
super().__init__(value=value, **kwargs)
|
525
|
|
|
526
|
7
|
def get_interact_value(self):
|
527
|
|
"""Return the value for this widget which should be passed to
|
528
|
|
interactive functions. Custom widgets can change this method
|
529
|
|
to process the raw value ``self.value``.
|
530
|
|
"""
|
531
|
0
|
return self.value
|