Ipywidgets: Widgets are not unique when stored in a dict in sub-class

Created on 7 Aug 2020  路  4Comments  路  Source: jupyter-widgets/ipywidgets

This may be a python feature rather than an ipywidgets bug, but thought it was worth pointing out in case someone else has a similar issue and can't figure it out.

I am subclassing HBox and VBox to create self-contained "combo-widgets". However, I noticed that when I made independent instances of my combo-widgets, the widgets they contained seemed to be connected. It turns out that because I was storing the widgets in a dict the widgets were pointing to the same address.

Here is an MCVE:

import ipywidgets as widgets
class myBox(widgets.HBox):
    def __init__(self):
        self.widgets['innerbox']=widgets.Text()
        super().__init__(children=[self.widgets['innerbox']])
b1 = myBox()
print(id(b1.widgets['innerbox']))
b2 = myBox()
print(id(b1.widgets['innerbox']), id(b2.widgets['innerbox']))

Result is

140583623982352 140583623982352

It doesn't happen if I store the inner widget like so

        self.innerbox=widgets.Text()

if I give the inner widgets different arguments every time they are instantiated they still end up pointing to the same place

import ipywidgets as widgets
import uuid
class myBox(widgets.HBox):
    def __init__(self):
        self.id = str(uuid.uuid1())
        self.widgets['innerbox']=widgets.Text(description=str(self.id))
        super().__init__(children=[self.widgets['innerbox']])
b1 = myBox()
print(b1.id, b1.widgets['innerbox'])
b2 = myBox()
print(b2.id, b2.widgets['innerbox'])
print(b1.id, b1.widgets['innerbox'])
print(id(b1.widgets['innerbox']), id(b2.widgets['innerbox']))
dfa1e66c-d865-11ea-bd85-fa163e8ac59c Text(value='', description='dfa1e66c-d865-11ea-bd85-fa163e8ac59c')
dfa587f4-d865-11ea-a034-fa163e8ac59c Text(value='', description='dfa587f4-d865-11ea-a034-fa163e8ac59c')
dfa1e66c-d865-11ea-bd85-fa163e8ac59c Text(value='', description='dfa587f4-d865-11ea-a034-fa163e8ac59c')
140583624267536 140583624267536

Most helpful comment

And I think the issue was coming up in this case because widgets is a class variable that is defined all the way back in the Widget base class

https://github.com/jupyter-widgets/ipywidgets/blob/6be18d9b75353f7b4a1c328c6ea06d8959f978f6/ipywidgets/widgets/widget.py#L266-L267

So the solution is either to redefine it for the instance as I did, or use a different variable name.

All 4 comments

I think this is an issue of class instance variables vs class variables. I just read https://stackoverflow.com/questions/45284838/are-the-attributes-in-a-python-class-shared-or-not which I found to be helpful for understanding this.

I think this issue is in how you are (or are not) initializing self.widgets. If I change your example to this:

import ipywidgets as widgets
class myBox(widgets.HBox):
    def __init__(self):
        self.widgets = {}
        self.widgets['innerbox']=widgets.Text()
        super().__init__(children=[self.widgets['innerbox']])
b1 = myBox()
b2 = myBox()
print(id(b1.widgets['innerbox']) == id(b2.widgets['innerbox']))
print(b1.widgets is b2.widgets)

then I get the result I think you are expecting.

False

And I think the issue was coming up in this case because widgets is a class variable that is defined all the way back in the Widget base class

https://github.com/jupyter-widgets/ipywidgets/blob/6be18d9b75353f7b4a1c328c6ea06d8959f978f6/ipywidgets/widgets/widget.py#L266-L267

So the solution is either to redefine it for the instance as I did, or use a different variable name.

I think we should not have used such a common name, or figured out some other way of having that global list. That's a good thing to look at changing for ipywidgets 8.

Thanks for the quick feedback and fix.

My initial motivation for using a dict to store the widgets was for convenience, so I pass a list of all widgets to the super call with list(self.widgets.keys()), and to clearly identify which of the class variables belong to a widet. In practice I didn't use the first idiom that much, as there weren't that many cases where the widget layout was that clean. It also relied on implicit dict ordering, which is problematic.

As for identifying widgets, I could (didn't) use a convention of prepending w_ to all my widget names, or something similar.

In the end I just went with storing them all as unique class attributes/variables and it works fine. This was mostly to draw attention to what was quite a perplexing issue for me, in case someone else had the same (not)bright idea.

Was this page helpful?
0 / 5 - 0 ratings