Fails to create unique keys in dict with lambda'ed widgets

Summary

I am generating 2 dicts with key -> lambda : <widget>, where the second is based on the keys of the first. When calling the widget in a for loop, I get a DuplicateWidgetID: There are multiple widgets with the same key='counts_bold_on'. error for the second dict but not the first.

When printing the keys, they are clearly unique. So I’m not sure how to fix it?

The reason for the implementation is that I want to generate the same section of a form for multiple elements. For each setting in a section, I want a) an “edit” button to indicate whether the b) widget should be used, and b) a widget for the setting. I know this not the prettiest design, but the code should work?

Steps to reproduce

Code snippet:

import streamlit as st

def create_font_settings(key_prefix: str):
    def make_key(key):
        return f"{key_prefix}_{key}_val"

    setting_widgets = {
        make_key("color"): lambda: st.color_picker(
            "Color",
            key=make_key("color"),
            value="#000000",
        ),
        make_key("bold"): lambda: st.checkbox(
            "Bold",
            key=make_key("bold"),
            value=False,
        ),
    }

    edit_widgets = {}
    for key in setting_widgets.keys():
        edit_key = f"{key[:-4]}_on"
        edit_widgets[edit_key] = lambda: st.checkbox(
            f"Edit",
            key=edit_key,
            value=False,
        )

    return setting_widgets, edit_widgets

output = {}

font_types = {
    "Counts": "counts",  # simplified
}

for font_type_pretty, font_type in font_types.items():
    st.subheader(font_type_pretty)
    num_cols = 3
    setting_widgets, edit_widgets = create_font_settings(
        key_prefix=font_type,
    )

    # Extract names without the "_val" suffix
    setting_names = [str(key)[:-4] for key in setting_widgets.keys()]

    # Some debugging info
    st.write("Setting names")
    st.write(setting_names)

    st.write("Widget dict")
    st.write(edit_widgets)

    for i, setting_name in enumerate(setting_names):
        if i % num_cols == 0:
            cols = st.columns(num_cols)
        with cols[i % num_cols]:
            # NOTE: Problem line on second iteration of the for loop!
            output[setting_name + "_on"] = edit_widgets[setting_name + "_on"]()

            # NOTE: The below works on its own... 
            # Disabled to show that the above is the problem.
            # output[setting_name + "_val"] = setting_widgets[setting_name + "_val"]()

If applicable, please provide the steps we should take to reproduce the error or specified behavior.

Expected behavior:

It should add both edit buttons without failing.

Actual behavior:

It creates the first edit button and then fails with the error.

Debug info

  • Streamlit version: 1.22
  • Python version: 3.11
  • Conda but pip install
  • OS version: osx 12.6
  • Browser version: Firefox 113

Workaround:

If I add an argument to the lambdas for taking the key, and use that to set the key within the widget, it works. This is a quite easy fix for my situation, but would still like to know, why the original code fails?

So lambda k: st.checkbox as such:

edit_widgets[edit_key] = lambda k: st.checkbox(
    f"Edit",
    key=k,
    value=False,
)

and when setting calling the widget, I pass the key:

output[setting_name + "_on"] = edit_widgets[setting_name + "_on"](setting_name + "_on")

They call it late binding, and it can be a trap.

1 Like

That explains it yes. Good to know! Thanks :slight_smile:

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.