##// END OF EJS Templates
Only set widget description in interact if it does not already exist
Jessica B. Hamrick -
Show More
@@ -1,256 +1,257 b''
1 1 """Interact with functions using widgets."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2013, the IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 from __future__ import print_function
16 16
17 17 try: # Python >= 3.3
18 18 from inspect import signature, Parameter
19 19 except ImportError:
20 20 from IPython.utils.signatures import signature, Parameter
21 21 from inspect import getcallargs
22 22
23 23 from IPython.core.getipython import get_ipython
24 24 from IPython.html.widgets import (Widget, TextWidget,
25 25 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
26 26 ContainerWidget, DOMWidget)
27 27 from IPython.display import display, clear_output
28 28 from IPython.utils.py3compat import string_types, unicode_type
29 29 from IPython.utils.traitlets import HasTraits, Any, Unicode
30 30
31 31 empty = Parameter.empty
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Classes and Functions
35 35 #-----------------------------------------------------------------------------
36 36
37 37
38 38 def _matches(o, pattern):
39 39 """Match a pattern of types in a sequence."""
40 40 if not len(o) == len(pattern):
41 41 return False
42 42 comps = zip(o,pattern)
43 43 return all(isinstance(obj,kind) for obj,kind in comps)
44 44
45 45
46 46 def _get_min_max_value(min, max, value=None, step=None):
47 47 """Return min, max, value given input values with possible None."""
48 48 if value is None:
49 49 if not max > min:
50 50 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
51 51 value = min + abs(min-max)/2
52 52 value = type(min)(value)
53 53 elif min is None and max is None:
54 54 if value == 0.0:
55 55 min, max, value = 0.0, 1.0, 0.5
56 56 elif value == 0:
57 57 min, max, value = 0, 1, 0
58 58 elif isinstance(value, (int, float)):
59 59 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
60 60 else:
61 61 raise TypeError('expected a number, got: %r' % value)
62 62 else:
63 63 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
64 64 if step is not None:
65 65 # ensure value is on a step
66 66 r = (value - min) % step
67 67 value = value - r
68 68 return min, max, value
69 69
70 70 def _widget_abbrev_single_value(o):
71 71 """Make widgets from single values, which can be used as parameter defaults."""
72 72 if isinstance(o, string_types):
73 73 return TextWidget(value=unicode_type(o))
74 74 elif isinstance(o, dict):
75 75 return DropdownWidget(values=o)
76 76 elif isinstance(o, bool):
77 77 return CheckboxWidget(value=o)
78 78 elif isinstance(o, float):
79 79 min, max, value = _get_min_max_value(None, None, o)
80 80 return FloatSliderWidget(value=o, min=min, max=max)
81 81 elif isinstance(o, int):
82 82 min, max, value = _get_min_max_value(None, None, o)
83 83 return IntSliderWidget(value=o, min=min, max=max)
84 84 else:
85 85 return None
86 86
87 87 def _widget_abbrev(o):
88 88 """Make widgets from abbreviations: single values, lists or tuples."""
89 89 float_or_int = (float, int)
90 90 if isinstance(o, (list, tuple)):
91 91 if o and all(isinstance(x, string_types) for x in o):
92 92 return DropdownWidget(values=[unicode_type(k) for k in o])
93 93 elif _matches(o, (float_or_int, float_or_int)):
94 94 min, max, value = _get_min_max_value(o[0], o[1])
95 95 if all(isinstance(_, int) for _ in o):
96 96 cls = IntSliderWidget
97 97 else:
98 98 cls = FloatSliderWidget
99 99 return cls(value=value, min=min, max=max)
100 100 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
101 101 step = o[2]
102 102 if step <= 0:
103 103 raise ValueError("step must be >= 0, not %r" % step)
104 104 min, max, value = _get_min_max_value(o[0], o[1], step=step)
105 105 if all(isinstance(_, int) for _ in o):
106 106 cls = IntSliderWidget
107 107 else:
108 108 cls = FloatSliderWidget
109 109 return cls(value=value, min=min, max=max, step=step)
110 110 else:
111 111 return _widget_abbrev_single_value(o)
112 112
113 113 def _widget_from_abbrev(abbrev, default=empty):
114 114 """Build a Widget instance given an abbreviation or Widget."""
115 115 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
116 116 return abbrev
117 117
118 118 widget = _widget_abbrev(abbrev)
119 119 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
120 120 # if it's not a single-value abbreviation,
121 121 # set the initial value from the default
122 122 try:
123 123 widget.value = default
124 124 except Exception:
125 125 # ignore failure to set default
126 126 pass
127 127 if widget is None:
128 128 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
129 129 return widget
130 130
131 131 def _yield_abbreviations_for_parameter(param, kwargs):
132 132 """Get an abbreviation for a function parameter."""
133 133 name = param.name
134 134 kind = param.kind
135 135 ann = param.annotation
136 136 default = param.default
137 137 not_found = (name, empty, empty)
138 138 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
139 139 if name in kwargs:
140 140 value = kwargs.pop(name)
141 141 elif ann is not empty:
142 142 value = ann
143 143 elif default is not empty:
144 144 value = default
145 145 else:
146 146 yield not_found
147 147 yield (name, value, default)
148 148 elif kind == Parameter.VAR_KEYWORD:
149 149 # In this case name=kwargs and we yield the items in kwargs with their keys.
150 150 for k, v in kwargs.copy().items():
151 151 kwargs.pop(k)
152 152 yield k, v, empty
153 153
154 154 def _find_abbreviations(f, kwargs):
155 155 """Find the abbreviations for a function and kwargs passed to interact."""
156 156 new_kwargs = []
157 157 for param in signature(f).parameters.values():
158 158 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
159 159 if value is empty:
160 160 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
161 161 new_kwargs.append((name, value, default))
162 162 return new_kwargs
163 163
164 164 def _widgets_from_abbreviations(seq):
165 165 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
166 166 result = []
167 167 for name, abbrev, default in seq:
168 168 widget = _widget_from_abbrev(abbrev, default)
169 widget.description = name
169 if not widget.description:
170 widget.description = name
170 171 result.append(widget)
171 172 return result
172 173
173 174 def interactive(__interact_f, **kwargs):
174 175 """Build a group of widgets to interact with a function."""
175 176 f = __interact_f
176 177 co = kwargs.pop('clear_output', True)
177 178 kwargs_widgets = []
178 179 container = ContainerWidget()
179 180 container.result = None
180 181 container.args = []
181 182 container.kwargs = dict()
182 183 kwargs = kwargs.copy()
183 184
184 185 new_kwargs = _find_abbreviations(f, kwargs)
185 186 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
186 187 # that will lead to a valid call of the function. This protects against unspecified
187 188 # and doubly-specified arguments.
188 189 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
189 190 # Now build the widgets from the abbreviations.
190 191 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
191 192
192 193 # This has to be done as an assignment, not using container.children.append,
193 194 # so that traitlets notices the update. We skip any objects (such as fixed) that
194 195 # are not DOMWidgets.
195 196 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
196 197 container.children = c
197 198
198 199 # Build the callback
199 200 def call_f(name, old, new):
200 201 container.kwargs = {}
201 202 for widget in kwargs_widgets:
202 203 value = widget.value
203 204 container.kwargs[widget.description] = value
204 205 if co:
205 206 clear_output(wait=True)
206 207 try:
207 208 container.result = f(**container.kwargs)
208 209 except Exception as e:
209 210 ip = get_ipython()
210 211 if ip is None:
211 212 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
212 213 else:
213 214 ip.showtraceback()
214 215
215 216 # Wire up the widgets
216 217 for widget in kwargs_widgets:
217 218 widget.on_trait_change(call_f, 'value')
218 219
219 220 container.on_displayed(lambda _: call_f(None, None, None))
220 221
221 222 return container
222 223
223 224 def interact(__interact_f=None, **kwargs):
224 225 """interact(f, **kwargs)
225 226
226 227 Interact with a function using widgets."""
227 228 # positional arg support in: https://gist.github.com/8851331
228 229 if __interact_f is not None:
229 230 # This branch handles the cases:
230 231 # 1. interact(f, **kwargs)
231 232 # 2. @interact
232 233 # def f(*args, **kwargs):
233 234 # ...
234 235 f = __interact_f
235 236 w = interactive(f, **kwargs)
236 237 f.widget = w
237 238 display(w)
238 239 return f
239 240 else:
240 241 # This branch handles the case:
241 242 # @interact(a=30, b=40)
242 243 # def f(*args, **kwargs):
243 244 # ...
244 245 def dec(f):
245 246 w = interactive(f, **kwargs)
246 247 f.widget = w
247 248 display(w)
248 249 return f
249 250 return dec
250 251
251 252 class fixed(HasTraits):
252 253 """A pseudo-widget whose value is fixed and never synced to the client."""
253 254 value = Any(help="Any Python object")
254 255 description = Unicode('', help="Any Python object")
255 256 def __init__(self, value, **kwargs):
256 257 super(fixed, self).__init__(value=value, **kwargs)
@@ -1,170 +1,170 b''
1 1 {
2 2 "metadata": {
3 3 "name": "",
4 "signature": "sha256:d75ab1c53fa3389eeac78ecf8e89beb52871950f296aad25776699b6d6125037"
4 "signature": "sha256:3f30c6e839ac39f890da34a2af6bf50bf0d99ea32f7aadc043f3e31f619e4bc9"
5 5 },
6 6 "nbformat": 3,
7 7 "nbformat_minor": 0,
8 8 "worksheets": [
9 9 {
10 10 "cells": [
11 11 {
12 12 "cell_type": "heading",
13 13 "level": 1,
14 14 "metadata": {},
15 15 "source": [
16 16 "Interact"
17 17 ]
18 18 },
19 19 {
20 20 "cell_type": "markdown",
21 21 "metadata": {},
22 22 "source": [
23 23 "The `interact` function provides a high-level interface for creating user interface controls to use in exploring code and data interactively."
24 24 ]
25 25 },
26 26 {
27 27 "cell_type": "code",
28 28 "collapsed": false,
29 29 "input": [
30 30 "from IPython.html.widgets import interact, interactive, fixed\n",
31 31 "from IPython.html import widgets\n",
32 32 "from IPython.display import clear_output, display, HTML"
33 33 ],
34 34 "language": "python",
35 35 "metadata": {},
36 36 "outputs": [],
37 37 "prompt_number": 1
38 38 },
39 39 {
40 40 "cell_type": "heading",
41 41 "level": 2,
42 42 "metadata": {},
43 43 "source": [
44 44 "Basic interact"
45 45 ]
46 46 },
47 47 {
48 48 "cell_type": "markdown",
49 49 "metadata": {},
50 50 "source": [
51 51 "Here is a simple function that displays its arguments as an HTML table:"
52 52 ]
53 53 },
54 54 {
55 55 "cell_type": "code",
56 56 "collapsed": false,
57 57 "input": [
58 58 "def show_args(**kwargs):\n",
59 59 " s = '<h3>Arguments:</h3><table>\\n'\n",
60 60 " for k,v in kwargs.items():\n",
61 61 " s += '<tr><td>{0}</td><td>{1}</td></tr>\\n'.format(k,v)\n",
62 62 " s += '</table>'\n",
63 63 " display(HTML(s))"
64 64 ],
65 65 "language": "python",
66 66 "metadata": {},
67 67 "outputs": [],
68 68 "prompt_number": 2
69 69 },
70 70 {
71 71 "cell_type": "code",
72 72 "collapsed": false,
73 73 "input": [
74 74 "show_args(a=10, b='Hi There', c=True)"
75 75 ],
76 76 "language": "python",
77 77 "metadata": {},
78 78 "outputs": [
79 79 {
80 80 "html": [
81 81 "<h3>Arguments:</h3><table>\n",
82 82 "<tr><td>a</td><td>10</td></tr>\n",
83 83 "<tr><td>c</td><td>True</td></tr>\n",
84 84 "<tr><td>b</td><td>Hi There</td></tr>\n",
85 85 "</table>"
86 86 ],
87 87 "metadata": {},
88 88 "output_type": "display_data",
89 89 "text": [
90 "<IPython.core.display.HTML at 0x10efc0d50>"
90 "<IPython.core.display.HTML object>"
91 91 ]
92 92 }
93 93 ],
94 94 "prompt_number": 3
95 95 },
96 96 {
97 97 "cell_type": "markdown",
98 98 "metadata": {},
99 99 "source": [
100 100 "Let's use this function to explore how `interact` works."
101 101 ]
102 102 },
103 103 {
104 104 "cell_type": "code",
105 105 "collapsed": false,
106 106 "input": [
107 107 "i = interact(show_args,\n",
108 108 " Temp=(0,10),\n",
109 109 " Current=(0.,10.,0.01),\n",
110 110 " z=True,\n",
111 111 " Text=u'Type here!',\n",
112 112 " #Algorithm=['This','That','Other'],\n",
113 " a=widgets.FloatSliderWidget(min=-10.0, max=10.0, step=0.1, value=5.0)\n",
113 " a=widgets.FloatSliderWidget(min=-10.0, max=10.0, step=0.1, value=5.0, description=\"Float (a)\")\n",
114 114 " )"
115 115 ],
116 116 "language": "python",
117 117 "metadata": {},
118 118 "outputs": [
119 119 {
120 120 "html": [
121 121 "<h3>Arguments:</h3><table>\n",
122 122 "<tr><td>Current</td><td>4.99</td></tr>\n",
123 123 "<tr><td>Text</td><td>Type here!</td></tr>\n",
124 124 "<tr><td>z</td><td>True</td></tr>\n",
125 "<tr><td>a</td><td>5.0</td></tr>\n",
126 125 "<tr><td>Temp</td><td>5</td></tr>\n",
126 "<tr><td>Float (a)</td><td>5.0</td></tr>\n",
127 127 "</table>"
128 128 ],
129 129 "metadata": {},
130 130 "output_type": "display_data",
131 131 "text": [
132 "<IPython.core.display.HTML at 0x10efcca10>"
132 "<IPython.core.display.HTML object>"
133 133 ]
134 134 }
135 135 ],
136 136 "prompt_number": 4
137 137 },
138 138 {
139 139 "cell_type": "code",
140 140 "collapsed": false,
141 141 "input": [
142 142 "i.widget"
143 143 ],
144 144 "language": "python",
145 145 "metadata": {},
146 146 "outputs": [
147 147 {
148 148 "html": [
149 149 "<h3>Arguments:</h3><table>\n",
150 150 "<tr><td>Current</td><td>4.99</td></tr>\n",
151 151 "<tr><td>Text</td><td>Type here!</td></tr>\n",
152 152 "<tr><td>z</td><td>True</td></tr>\n",
153 "<tr><td>a</td><td>5.0</td></tr>\n",
154 153 "<tr><td>Temp</td><td>5</td></tr>\n",
154 "<tr><td>Float (a)</td><td>5.0</td></tr>\n",
155 155 "</table>"
156 156 ],
157 157 "metadata": {},
158 158 "output_type": "display_data",
159 159 "text": [
160 "<IPython.core.display.HTML at 0x10f027050>"
160 "<IPython.core.display.HTML object>"
161 161 ]
162 162 }
163 163 ],
164 164 "prompt_number": 5
165 165 }
166 166 ],
167 167 "metadata": {}
168 168 }
169 169 ]
170 170 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now