gradio-app / gradio

Build and share delightful machine learning apps, all in Python. šŸŒŸ Star to support our work!
http://www.gradio.app
Apache License 2.0
32.03k stars 2.39k forks source link

Component values are not updated in outside function #7541

Closed DRomatzki closed 6 months ago

DRomatzki commented 6 months ago

Describe the bug

I used a slider and a textbox. On slider change the value in the text box changes. However, there is a button and when pressed it calls a function outside the gr.Blocks code, and prints the values of the slider and textbox. they are different from what is shown.. It seems the values of the gr objects are not updated correctly or accessing the gr components outside the gr Blocks. is not practical. Unfortunately I consider this as a bug and is critical since putting all your code in a single module is confusing and difficult to debug. I can not write functions for every gr object because there an more than 20 variables. Maybe there is a workaround but any workaround would be inefficient and possible impractical. I have no idea how to solve it except by writing multiple repeat lines of code. State variables?

If I missed something , my apologies.

Have you searched existing issues? šŸ”Ž

Reproduction

import gradio as gr

def print_local():
    print("My slider Value ",my_slider.value)
    print("My textbox Value ",my_text.value)
    return

# Display the interface using Blocks context manager
with gr.Blocks() as iface:
    my_button1= gr.Button(value="Show values")
    my_text=gr.Textbox(label="Set Value",value=0)

    my_slider=gr.Slider(label="Slider value", step=1,minimum=0, maximum=20,value=2)

    my_button1.click(print_local,None,None)
    my_slider.change(lambda x:x,my_slider,my_text)

    iface.launch()

### Screenshot

No screenshot. only bash window

### Logs

```shell
NO Logs

System Info

Gradio Environment Information:
------------------------------
Operating System: Linux
gradio version: 4.16.0
gradio_client version: 0.8.1

------------------------------------------------
gradio dependencies in your environment:

aiofiles: 23.2.1
altair: 5.2.0
fastapi: 0.109.0
ffmpy: 0.3.1
gradio-client==0.8.1 is not installed.
httpx: 0.26.0
huggingface-hub: 0.20.3
importlib-resources: 6.1.1
jinja2: 3.1.3
markupsafe: 2.1.4
matplotlib: 3.8.2
numpy: 1.26.3
orjson: 3.9.12
packaging: 23.2
pandas: 2.2.0
pillow: 10.2.0
pydantic: 2.5.3
pydub: 0.25.1
python-multipart: 0.0.6
pyyaml: 6.0.1
ruff: 0.1.14
semantic-version: 2.10.0
tomlkit==0.12.0 is not installed.
typer: 0.9.0
typing-extensions: 4.9.0
uvicorn: 0.27.0
authlib; extra == 'oauth' is not installed.
itsdangerous; extra == 'oauth' is not installed.

gradio_client dependencies in your environment:

fsspec: 2023.12.2
httpx: 0.26.0
huggingface-hub: 0.20.3
packaging: 23.2
typing-extensions: 4.9.0
websockets: 11.0.3

Severity

Blocking usage of gradio

abidlabs commented 6 months ago

Hi @DRomatzki sorry I don't quite follow what the problem is. To access the value of components, you need to pass them as inputs to your event (e.g. .click()), which passes the values of these components as parameters in your function. Are you saying that you have too many input components, and its inconvenient to write them all out explicitly as inputs to your event?

DRomatzki commented 6 months ago

Yes. I am writing a GUI to set the configuration parameters for a server. There are about 20 parameters. So now writing a .change event for each of them clogs the module and is counter productive. In my example the slider changes value and the textbox is updated as per .change event. However, if you then read the value of the textbox from a function, it returns the original value, before the slider has changed it, and not the update value. I thought that was clear. I am not familiar with your source code but is this not part of the post-process part? I have registered another bug of a dropdown box which gives a warning when you enter a value which is actually in the choices. That was about 2 weeks ago. The solution you found was that you used the old value and not the new value in the post-process, or something like that. This problem might be the same here. You do not update the changed value of the textbox value parameter in the post process. I think a workaround would be to update statev with each change event, but that is stupid. Querying a gradio object must return the current value, not the value set in the previous event.

abidlabs commented 6 months ago

Hi @DRomatzki I'm sorry I'm having a hard time following what you've described. To be clear, you cannot access the latest value of a component using the .value attribute. You must pass in the components as inputs.

Some things that may help you: *. You can bind multiple event triggers together using gr.on: https://www.gradio.app/guides/blocks-and-event-listeners#binding-multiple-triggers-to-a-function

DRomatzki commented 6 months ago

Hi again. I think we misunderstand each other. Have you looked at the printed values in the terminal from the function def print_local():?

Looking at the code it all seems fine and simple. Running it, however, is something different. 1) Run the code 2) Change the slider value to , say, 4. 3) The textbox is updated to "4" via the .change event 4) Press the button and check the terminal for the printed output. 5) The result printed is textbox 0, slider 2. These are the initial values and not the new values.

If you look at my example code you will see an event trigger, my_slider.change(lambda x:x,my_slider,my_text). In this case the value of the slider is transferred to the textbox. That is correct as you say and it happens. The textbox GUI is updated and we can see it. Now I want to access the new updated textbox value and I do it from a function., def print_local():. That is also correct. That means I am trying to access the new and updated property from the objects. This can be done and is fully compliant with your code base. However, I found out that the supposedly updated property of the object has not been updated and are still the old values. That is my point and a bug in my opinion. Please look at the terminal print out. My code does everything you say. It uses events to update other components on user input, except the visual part is updated but not the backend values. I do not know how else to explain. The backend values of objects are not what the visual values of these objects show. I can only presume that the same code on your system behaves differently than on mine but that is highly unlikely. Please compare the printed output from "def print_local():" in the terminal with the GUI values. Personnally I believe it is in your post processing routine when object values change. You update the GUI part of the object but not the property ".value" A work around from my side would be to assign an elem_ID to each object and then query the actual value of the object as it is displayed in the browser using js or something., But that is not how it is supposed to be. Instead of printing the values I want to use the updated values that the user has changed for other purposes. I do not know what the other people did previously. Am I making a mistake? Again... run the code and look at the printout in the terminal. The printed values do NOT correspond with the GUI shown values.

abidlabs commented 6 months ago

Hi @DRomatzki this is actually the expected behavior. When you directly access the attributes of a component (e.g. .value or.lines), you will get the initial value of that attribute. This is what is defined in the backend and this does not change. We couldn't change it because these component objects are shared across all users of an app (since there's only one backend serving many different users). This is why we never suggest users accessing attributes of a component directly.

When you update a property, what happens is that we actually create a copy of a component and store it a dictionary that is keyed by your browser session ID. This component is used and updated as needed.

If you would ever need to access the updated properties of a component, you should pass it in as an input to your function using an event listener, like you do here:

my_slider.change(lambda x:x,my_slider,my_text)

This is expected behavior, not a bug so I'll go ahead and close this issue

DRomatzki commented 6 months ago

HI again Before I start my last argument... I would like to thank you for your patience and the brilliant code, which is gradio. Thanks a lot. My argument is the scope of the gradio objects..... The way that I now understand is that a copy is made when the object is created, meaning my_text-gr.text(). All action, events, etc are now applicable to that copied object and the backend is ignored since we are now in a session. This means the scope of the object is now in the session iface.launch() and therefor its scope must be in that session. All references to that object must be on the copy and not the backend. The backend is not needed at that point anymore. Howewver, there are now 2 interface (or scopes), the interface and the backend. I still consider this as a bug because accessing the objects value must be in the scope of the interface, Sorry for arguing but this is how I see it.

abidlabs commented 6 months ago

I understand what you're saying @DRomatzki, but that isn't technically possible, because in Python, we can't change what a variable that a developer has defined points to. In your example, my_slider will always point to the original gr.Slider that you've defined. That's why we never encourage users to access attributes of components directly. Is there a particular example that you saw that does this? In the Gradio docs, afaik, we always mention passing components objects in event handlers if you want to get their (updated) value in a function

DRomatzki commented 6 months ago

One last shot.. please. I thank you again for your patience... you certainly have a lot. :-) BTW I have upgrade gradio to the latest version .4.19.* (I cant remember the last digits. I have read the referenced pages mentioned above and noticed the list that can be passed. I have used that in my code already. however, your suggestion of set(iface.components) does not work. The error is my_set=set(iface.components) AttributeError: 'Blocks' object has no attribute 'components' So I played around and modified the code as follows.

import gradio as gr

my_list={}

def process_list(*args):

Iterate over the indices of the tuple

for i in range(len(args)):
    print(my_list[i], args[i])

Display the interface using Blocks context manager

with gr.Blocks() as iface: my_button= gr.Button(value="Show values",elem_id="my_button1") my_text=gr.Textbox(label="Set Value",value=0,elem_id="my_text")

my_slider=gr.Slider(label="Slider value", step=1,minimum=0, maximum=20,value=2,elem_id="my_slider_1")

my_set=list(iface.children)
for i in range(len(my_set)):
    my_list[i]=iface.children[i].elem_id

my_button.click(process_list,my_set,None)
my_slider.change(lambda x:x,my_slider,my_text)

iface.launch()

It now works! If you other ideas that work better please let me know. Thanks again!

abidlabs commented 6 months ago

Yes sorry .components was incorrect. .children is fine, or doing iface.blocks will also work I think