Repository: gradio-app/gradio

Issue:
gr.update on multiple output components errors when output created using list multiplication but succeeds with list comprehension
### Describe the bug

`[gr.update(visible=value) for _ in range(2)]` works.
`[gr.update(visible=value)]*2` gives error below.

Toggle the checkbox to trigger.

I printed in the function body to check differences in the 2 variants. They look the same.
```
True
variant1: [{'visible': False, '__type__': 'generic_update'}, {'visible': False, '__type__': 'generic_update'}]
variant2: [{'visible': False, '__type__': 'generic_update'}, {'visible': False, '__type__': 'generic_update'}]
```

I tried debugging the python file and see that the failure variant can stop on my breakpoint at `gradio/components.py", line 1534, in postprocess` but the successful variant does not stop at the breakpoint? 
Why is the successful variant working? And why is it not stopping at my breakpoint like the failure variant does? Are they going down different code paths?

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Reproduction

```
import gradio as gr

num_images = 2

with gr.Blocks() as demo:
    im_list = [gr.Image() for i in range(num_images)]
    def change_visibility(value):
        variant1 = [gr.update(visible=value) for _ in range(2)]
        variant2 = [gr.update(visible=value)]*2
        
        print(variant1 == variant2)
        print('variant1:', variant1)
        print('variant2:', variant2)
        
        # return variant1
        return variant2
    
    checkbox = gr.Checkbox(value=True, label='Show image1')
    checkbox.change(change_visibility, inputs=checkbox,outputs=im_list) 
    
if __name__ == "__main__":
    demo.launch()
```

### Screenshot

_No response_

### Logs

```shell
Traceback (most recent call last):
  File "/Users/hanqi/code/ahrefs/venv/lib/python3.8/site-packages/gradio/routes.py", line 384, in run_predict
    output = await app.get_blocks().process_api(
  File "/Users/hanqi/code/ahrefs/venv/lib/python3.8/site-packages/gradio/blocks.py", line 1027, in process_api
    data = self.postprocess_data(fn_index, result["prediction"], state)
  File "/Users/hanqi/code/ahrefs/venv/lib/python3.8/site-packages/gradio/blocks.py", line 964, in postprocess_data
    prediction_value = block.postprocess(prediction_value)
  File "/Users/hanqi/code/ahrefs/venv/lib/python3.8/site-packages/gradio/components.py", line 1534, in postprocess
    raise ValueError("Cannot process this value as an Image")
ValueError: Cannot process this value as an Image
```
```


### System Info

```shell
Python 3.8.12 (default, Dec  8 2021, 11:22:00) 
[Clang 12.0.5 (clang-1205.0.22.11)] 
Gradio version 3.19.1
MacOS Big Sur version 11.3
```


### Severity

annoying


Discussion:
So strange, I can confirm the issue but have no idea what could be causing this! We'll look into it, thanks for reporting @gitgithan!
Thanks for filing @gitgithan !

The [prostprocessing](https://github.com/gradio-app/gradio/blob/main/gradio/blocks.py#L360) of update dictionaries involves deleting keys from the dictionary, which is why shallow copies will not work as expected. 

Maybe this can be rewritten to not modify the dictionaries in place. 
Ah nice catch @freddyaboulton! That should fix it. @gitgithan would you be interested in making a PR? 
@abidlabs Yes I'm interested in looking at how to fix this, it has caused me immeasurable pain today.
Could I get some guidance on how to trace the execution so I understand what's going on? 
I assume I'll need to know the code paths for both variants to compare differences, and to ensure relevant files are considered/editted.
@freddyaboulton 's comment and link seems to have dived straight into the source of the issue but I only have 1 day experience on Gradio so far so it's over my head
Thanks for wanting to take a look at this @gitgithan !

The logic for processing the user defined functions in the backend is pretty much handled entirely by [process_api](https://github.com/gradio-app/gradio/blob/main/gradio/blocks.py#L969).

There are three parts:
1. Preprocessing the data from the front-end
2. Calling the user defined function with this data
3. post processing the data returned from the function

If you want to wrap your head around this, I would set a `breakpoint()` in `process_api` and then launch a simple demo and step through it.

I think this particular problem is caused in this [part](https://github.com/gradio-app/gradio/blob/main/gradio/blocks.py#L955) of the postprocessing, where we apply special processing for update dictionaries. 

I think just modifying that function to return a new copy of the update dict with the keys removed, as opposed to removing the keys in-place should fix the issue.
Hi @gitgithan , wondering if you're still interested in helping us fix this issue!

Resolving patch:
diff --git a/gradio/blocks.py b/gradio/blocks.py
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -251,6 +251,7 @@ def update(**kwargs) -> Dict:
 
     @classmethod
     def get_specific_update(cls, generic_update: Dict[str, Any]) -> Dict:
+        generic_update = generic_update.copy()
         del generic_update["__type__"]
         specific_update = cls.update(**generic_update)
         return specific_update


Test patch:
diff --git a/test/test_blocks.py b/test/test_blocks.py
--- a/test/test_blocks.py
+++ b/test/test_blocks.py
@@ -484,6 +484,22 @@ def test_blocks_does_not_replace_keyword_literal(self):
         output = demo.postprocess_data(0, gr.update(value="NO_VALUE"), state={})
         assert output[0]["value"] == "NO_VALUE"
 
+    def test_blocks_does_not_del_dict_keys_inplace(self):
+        with gr.Blocks() as demo:
+            im_list = [gr.Image() for i in range(2)]
+
+            def change_visibility(value):
+                return [gr.update(visible=value)] * 2
+
+            checkbox = gr.Checkbox(value=True, label="Show image")
+            checkbox.change(change_visibility, inputs=checkbox, outputs=im_list)
+
+        output = demo.postprocess_data(0, [gr.update(visible=False)] * 2, state={})
+        assert output == [
+            {"visible": False, "__type__": "update"},
+            {"visible": False, "__type__": "update"},
+        ]
+
     def test_blocks_returns_correct_output_dict_single_key(self):
         with gr.Blocks() as demo:
             num = gr.Number()