##// END OF EJS Templates
Clean up some docstring formatting for interact/interactive
Thomas Kluyver -
Show More
@@ -1,344 +1,344 b''
1 1 """Interact with functions using widgets."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import print_function
7 7
8 8 try: # Python >= 3.3
9 9 from inspect import signature, Parameter
10 10 except ImportError:
11 11 from IPython.utils.signatures import signature, Parameter
12 12 from inspect import getcallargs
13 13
14 14 from IPython.core.getipython import get_ipython
15 15 from IPython.html.widgets import (Widget, Text,
16 16 FloatSlider, IntSlider, Checkbox, Dropdown,
17 17 Box, Button, DOMWidget)
18 18 from IPython.display import display, clear_output
19 19 from IPython.utils.py3compat import string_types, unicode_type
20 20 from IPython.utils.traitlets import HasTraits, Any, Unicode
21 21
22 22 empty = Parameter.empty
23 23
24 24
25 25 def _matches(o, pattern):
26 26 """Match a pattern of types in a sequence."""
27 27 if not len(o) == len(pattern):
28 28 return False
29 29 comps = zip(o,pattern)
30 30 return all(isinstance(obj,kind) for obj,kind in comps)
31 31
32 32
33 33 def _get_min_max_value(min, max, value=None, step=None):
34 34 """Return min, max, value given input values with possible None."""
35 35 if value is None:
36 36 if not max > min:
37 37 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
38 38 value = min + abs(min-max)/2
39 39 value = type(min)(value)
40 40 elif min is None and max is None:
41 41 if value == 0.0:
42 42 min, max, value = 0.0, 1.0, 0.5
43 43 elif value == 0:
44 44 min, max, value = 0, 1, 0
45 45 elif isinstance(value, (int, float)):
46 46 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
47 47 else:
48 48 raise TypeError('expected a number, got: %r' % value)
49 49 else:
50 50 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
51 51 if step is not None:
52 52 # ensure value is on a step
53 53 r = (value - min) % step
54 54 value = value - r
55 55 return min, max, value
56 56
57 57 def _widget_abbrev_single_value(o):
58 58 """Make widgets from single values, which can be used as parameter defaults."""
59 59 if isinstance(o, string_types):
60 60 return Text(value=unicode_type(o))
61 61 elif isinstance(o, dict):
62 62 return Dropdown(options=o)
63 63 elif isinstance(o, bool):
64 64 return Checkbox(value=o)
65 65 elif isinstance(o, float):
66 66 min, max, value = _get_min_max_value(None, None, o)
67 67 return FloatSlider(value=o, min=min, max=max)
68 68 elif isinstance(o, int):
69 69 min, max, value = _get_min_max_value(None, None, o)
70 70 return IntSlider(value=o, min=min, max=max)
71 71 else:
72 72 return None
73 73
74 74 def _widget_abbrev(o):
75 75 """Make widgets from abbreviations: single values, lists or tuples."""
76 76 float_or_int = (float, int)
77 77 if isinstance(o, (list, tuple)):
78 78 if o and all(isinstance(x, string_types) for x in o):
79 79 return Dropdown(options=[unicode_type(k) for k in o])
80 80 elif _matches(o, (float_or_int, float_or_int)):
81 81 min, max, value = _get_min_max_value(o[0], o[1])
82 82 if all(isinstance(_, int) for _ in o):
83 83 cls = IntSlider
84 84 else:
85 85 cls = FloatSlider
86 86 return cls(value=value, min=min, max=max)
87 87 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
88 88 step = o[2]
89 89 if step <= 0:
90 90 raise ValueError("step must be >= 0, not %r" % step)
91 91 min, max, value = _get_min_max_value(o[0], o[1], step=step)
92 92 if all(isinstance(_, int) for _ in o):
93 93 cls = IntSlider
94 94 else:
95 95 cls = FloatSlider
96 96 return cls(value=value, min=min, max=max, step=step)
97 97 else:
98 98 return _widget_abbrev_single_value(o)
99 99
100 100 def _widget_from_abbrev(abbrev, default=empty):
101 101 """Build a Widget instance given an abbreviation or Widget."""
102 102 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
103 103 return abbrev
104 104
105 105 widget = _widget_abbrev(abbrev)
106 106 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
107 107 # if it's not a single-value abbreviation,
108 108 # set the initial value from the default
109 109 try:
110 110 widget.value = default
111 111 except Exception:
112 112 # ignore failure to set default
113 113 pass
114 114 if widget is None:
115 115 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
116 116 return widget
117 117
118 118 def _yield_abbreviations_for_parameter(param, kwargs):
119 119 """Get an abbreviation for a function parameter."""
120 120 name = param.name
121 121 kind = param.kind
122 122 ann = param.annotation
123 123 default = param.default
124 124 not_found = (name, empty, empty)
125 125 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
126 126 if name in kwargs:
127 127 value = kwargs.pop(name)
128 128 elif ann is not empty:
129 129 value = ann
130 130 elif default is not empty:
131 131 value = default
132 132 else:
133 133 yield not_found
134 134 yield (name, value, default)
135 135 elif kind == Parameter.VAR_KEYWORD:
136 136 # In this case name=kwargs and we yield the items in kwargs with their keys.
137 137 for k, v in kwargs.copy().items():
138 138 kwargs.pop(k)
139 139 yield k, v, empty
140 140
141 141 def _find_abbreviations(f, kwargs):
142 142 """Find the abbreviations for a function and kwargs passed to interact."""
143 143 new_kwargs = []
144 144 for param in signature(f).parameters.values():
145 145 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
146 146 if value is empty:
147 147 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
148 148 new_kwargs.append((name, value, default))
149 149 return new_kwargs
150 150
151 151 def _widgets_from_abbreviations(seq):
152 152 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
153 153 result = []
154 154 for name, abbrev, default in seq:
155 155 widget = _widget_from_abbrev(abbrev, default)
156 156 if not widget.description:
157 157 widget.description = name
158 158 widget._kwarg = name
159 159 result.append(widget)
160 160 return result
161 161
162 162 def interactive(__interact_f, **kwargs):
163 163 """
164 164 Builds a group of interactive widgets tied to a function and places the
165 165 group into a Box container.
166 166
167 167 Returns
168 168 -------
169 169 container : a Box instance containing multiple widgets
170 170
171 171 Parameters
172 172 ----------
173 173 __interact_f : function
174 The function to which the interactive widgets are tied. The **kwargs
174 The function to which the interactive widgets are tied. The `**kwargs`
175 175 should match the function signature.
176 176 **kwargs : various, optional
177 177 An interactive widget is created for each keyword argument that is a
178 178 valid widget abbreviation.
179 179 """
180 180 f = __interact_f
181 181 co = kwargs.pop('clear_output', True)
182 182 manual = kwargs.pop('__manual', False)
183 183 kwargs_widgets = []
184 184 container = Box()
185 185 container.result = None
186 186 container.args = []
187 187 container.kwargs = dict()
188 188 kwargs = kwargs.copy()
189 189
190 190 new_kwargs = _find_abbreviations(f, kwargs)
191 191 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
192 192 # that will lead to a valid call of the function. This protects against unspecified
193 193 # and doubly-specified arguments.
194 194 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
195 195 # Now build the widgets from the abbreviations.
196 196 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
197 197
198 198 # This has to be done as an assignment, not using container.children.append,
199 199 # so that traitlets notices the update. We skip any objects (such as fixed) that
200 200 # are not DOMWidgets.
201 201 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
202 202
203 203 # If we are only to run the function on demand, add a button to request this
204 204 if manual:
205 205 manual_button = Button(description="Run %s" % f.__name__)
206 206 c.append(manual_button)
207 207 container.children = c
208 208
209 209 # Build the callback
210 210 def call_f(name=None, old=None, new=None):
211 211 container.kwargs = {}
212 212 for widget in kwargs_widgets:
213 213 value = widget.value
214 214 container.kwargs[widget._kwarg] = value
215 215 if co:
216 216 clear_output(wait=True)
217 217 if manual:
218 218 manual_button.disabled = True
219 219 try:
220 220 container.result = f(**container.kwargs)
221 221 except Exception as e:
222 222 ip = get_ipython()
223 223 if ip is None:
224 224 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
225 225 else:
226 226 ip.showtraceback()
227 227 finally:
228 228 if manual:
229 229 manual_button.disabled = False
230 230
231 231 # Wire up the widgets
232 232 # If we are doing manual running, the callback is only triggered by the button
233 233 # Otherwise, it is triggered for every trait change received
234 234 # On-demand running also suppresses running the function with the initial parameters
235 235 if manual:
236 236 manual_button.on_click(call_f)
237 237 else:
238 238 for widget in kwargs_widgets:
239 239 widget.on_trait_change(call_f, 'value')
240 240
241 241 container.on_displayed(lambda _: call_f(None, None, None))
242 242
243 243 return container
244 244
245 245 def interact(__interact_f=None, **kwargs):
246 246 """
247 247 Displays interactive widgets which are tied to a function.
248 248 Expects the first argument to be a function. Parameters to this function are
249 widget abbreviations passed in as keyword arguments (**kwargs). Can be used
249 widget abbreviations passed in as keyword arguments (`**kwargs`). Can be used
250 250 as a decorator (see examples).
251 251
252 252 Returns
253 253 -------
254 254 f : __interact_f with interactive widget attached to it.
255 255
256 256 Parameters
257 257 ----------
258 258 __interact_f : function
259 The function to which the interactive widgets are tied. The **kwargs
259 The function to which the interactive widgets are tied. The `**kwargs`
260 260 should match the function signature. Passed to :func:`interactive()`
261 261 **kwargs : various, optional
262 262 An interactive widget is created for each keyword argument that is a
263 263 valid widget abbreviation. Passed to :func:`interactive()`
264 264
265 265 Examples
266 266 --------
267 Renders an interactive text field that shows the greeting with the passed in
268 text.
267 Render an interactive text field that shows the greeting with the passed in
268 text::
269 269
270 1. Invocation of interact as a function
270 # 1. Using interact as a function
271 271 def greeting(text="World"):
272 272 print "Hello {}".format(text)
273 273 interact(greeting, text="IPython Widgets")
274 274
275 2. Invocation of interact as a decorator
275 # 2. Using interact as a decorator
276 276 @interact
277 277 def greeting(text="World"):
278 278 print "Hello {}".format(text)
279 279
280 3. Invocation of interact as a decorator with named parameters
280 # 3. Using interact as a decorator with named parameters
281 281 @interact(text="IPython Widgets")
282 282 def greeting(text="World"):
283 283 print "Hello {}".format(text)
284 284
285 Renders an interactive slider widget and prints square of number.
285 Render an interactive slider widget and prints square of number::
286 286
287 1. Invocation of interact as a function
287 # 1. Using interact as a function
288 288 def square(num=1):
289 289 print "{} squared is {}".format(num, num*num)
290 290 interact(square, num=5)
291 291
292 2. Invocation of interact as a decorator
292 # 2. Using interact as a decorator
293 293 @interact
294 294 def square(num=2):
295 295 print "{} squared is {}".format(num, num*num)
296 296
297 3. Invocation of interact as a decorator with named parameters
297 # 3. Using interact as a decorator with named parameters
298 298 @interact(num=5)
299 299 def square(num=2):
300 300 print "{} squared is {}".format(num, num*num)
301 301 """
302 302 # positional arg support in: https://gist.github.com/8851331
303 303 if __interact_f is not None:
304 304 # This branch handles the cases 1 and 2
305 305 # 1. interact(f, **kwargs)
306 306 # 2. @interact
307 307 # def f(*args, **kwargs):
308 308 # ...
309 309 f = __interact_f
310 310 w = interactive(f, **kwargs)
311 311 try:
312 312 f.widget = w
313 313 except AttributeError:
314 314 # some things (instancemethods) can't have attributes attached,
315 315 # so wrap in a lambda
316 316 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
317 317 f.widget = w
318 318 display(w)
319 319 return f
320 320 else:
321 321 # This branch handles the case 3
322 322 # @interact(a=30, b=40)
323 323 # def f(*args, **kwargs):
324 324 # ...
325 325 def dec(f):
326 326 return interact(f, **kwargs)
327 327 return dec
328 328
329 329 def interact_manual(__interact_f=None, **kwargs):
330 330 """interact_manual(f, **kwargs)
331 331
332 332 As `interact()`, generates widgets for each argument, but rather than running
333 333 the function after each widget change, adds a "Run" button and waits for it
334 334 to be clicked. Useful if the function is long-running and has several
335 335 parameters to change.
336 336 """
337 337 return interact(__interact_f, __manual=True, **kwargs)
338 338
339 339 class fixed(HasTraits):
340 340 """A pseudo-widget whose value is fixed and never synced to the client."""
341 341 value = Any(help="Any Python object")
342 342 description = Unicode('', help="Any Python object")
343 343 def __init__(self, value, **kwargs):
344 344 super(fixed, self).__init__(value=value, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now