Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document widget.observe(..., names) #3192

Open
thomasaarholt opened this issue May 3, 2021 · 4 comments
Open

Document widget.observe(..., names) #3192

thomasaarholt opened this issue May 3, 2021 · 4 comments
Labels

Comments

@thomasaarholt
Copy link

I am currently writing a documentation PR to add some examples to the widget.observe behaviour, and have come across some things that I need help clarifying. If anyone has anything enlightning, please stick it in this issue and I will incorporate it into a PR.

Problem

widget.observe() lets one specify what sort of widget "action" should trigger a function by using the names keyword. It's quite hard for the user to learn what sort of values to give it.

By experimenting, I've found that generally the following output is generated with the default value of traitlets.All:

from ipywidgets import IntSlider, Label, Output, VBox

o1 = Output()
def print_output(value):
    with o1:
        print(value)
        
slider = IntSlider(50, min=1, max=100, step=1)

slider.observe(print_output)
display(VBox([slider, o1]))

# Adjust the slider once

{'name': '_property_lock', 'old': traitlets.Undefined, 'new': {'value': 72}, 'owner': IntSlider(value=50, min=1), 'type': 'change'}
{'name': 'value', 'old': 50, 'new': 72, 'owner': IntSlider(value=72, min=1), 'type': 'change'}
{'name': '_property_lock', 'old': {'value': 72}, 'new': {}, 'owner': IntSlider(value=72, min=1), 'type': 'change'}

It's not clear to me why '_property_lock' appears (twice!) in this output.

Current accepted values include: [traitlets.All, 'value', '_property_lock']. Are there more?

Suggested Improvement

  • Improve docstring and online documentation with a list of names and what they mean
  • Should _property_lock be a private name? It seems to be user-facing.
  • Clarify (why?) the default value is names=traitlets.All.
  • We need to clarify what _property_lock is - I would have guessed that it was some sort of "slider freezer" at first, but that doesn't quite add up with its value being a dict.
@ianhi
Copy link
Contributor

ianhi commented May 3, 2021

First of all thank you again for tackling that - it's definitely a weak point of the docs!


The observe function is actually directly inherited from traitlets.HasTraits (https://traitlets.readthedocs.io/en/stable/api.html#traitlets.HasTraits.observe) so traitlets.All just refers to every trait that is defined for the class you happen to be using. So IntSliders will have:
value:

value = CInt(0, help="Int value").tag(sync=True)

min and max

max = CInt(100, help="Max value").tag(sync=True)
min = CInt(0, help="Min value").tag(sync=True)

all of these:
_view_name = Unicode('IntSliderView').tag(sync=True)
_model_name = Unicode('IntSliderModel').tag(sync=True)
step = CInt(1, help="Minimum step to increment the value").tag(sync=True)
orientation = CaselessStrEnum(values=['horizontal', 'vertical'],
default_value='horizontal', help="Vertical or horizontal.").tag(sync=True)
readout = Bool(True, help="Display the current value of the slider next to it.").tag(sync=True)
readout_format = NumberFormat(
'd', help="Format for the readout").tag(sync=True)
continuous_update = Bool(True, help="Update the value of the widget as the user is holding the slider.").tag(sync=True)
disabled = Bool(False, help="Enable or disable user changes").tag(sync=True)
style = InstanceDict(SliderStyle).tag(sync=True, **widget_serialization)

and things like _property_lock that come all the way from the Widgets base class:

_property_lock = Dict()
_holding_sync = False
_states_to_send = Set()
_msg_callbacks = Instance(CallbackDispatcher, ())

here is a (hopefully simple) example of how traitlets.All in observe works. Any trait that you add the class will be observed and the callback will fire if that trait changes.

from traitlets import HasTraits, All, Int, Unicode
class widget(HasTraits):
    # all attributes defined as traitlets like this will
    # be included in traitlets.All
    beep = Int(2)
    boop = Unicode('blah blah')
    _property_lock = Int(2) # this is just something defined by ipywidgets - nothing instrinsically special
    
def cb(change):
    print(change)

obj = widget()
obj.observe(cb)

# property lock probably gets set on init
obj._property_lock = 0

obj.beep = 3
obj.boop = '2323'

# sometimes it gets updated along with a values
obj._property_lock = 2

To your specific questions:

It's not clear to me why '_property_lock' appears (twice!) in this output.

If you look I think it gets updated anytime a trait that is synced to the frontend changes. So it's set once on init and again when you modify the value, hence two changes.

Should _property_lock be a private name? It seems to be user-facing.

I think it essentially is - but a non-optimal default value to observe is sort of exposing it. I think that best practice to always explicitly use the names argument to observe.

Clarify (why?) the default value is names=traitlets.All.

This is inherited from traitlets where All is probably a reasonable default because they have no idea how they will be used. I think there is an argument to be made that we should override observe to give a default value to names that is specific to each widget. e.g. for IntSlider you almost always want to observe value and nothing else.

We need to clarify what _property_lock is - I would have guessed that it was some sort of "slider freezer" at first, but that doesn't quite add up with its value being a dict.

I have no idea what this does - something with syncing values behind the scenes maybe?

@ianhi
Copy link
Contributor

ianhi commented May 3, 2021

Related issue: #1680

@landonwork
Copy link

Is this an okay place to mention that the observe documentation example with the slider is broken? The text above the slider in the example never updates/re-renders.
https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#registering-callbacks-to-trait-changes-in-the-kernel

@jasongrout
Copy link
Member

Is this an okay place to mention that the observe documentation example with the slider is broken? The text above the slider in the example never updates/re-renders.

The documentation webpage is not connected to a live kernel. Copying that code into a notebook and running it should update the text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants