Repository: streamlit/streamlit
Issue: st.data_editor is not stateful when styling disabled columns

### Checklist

- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.
- [X] I added a very descriptive title to this issue.
- [X] I have provided sufficient information below to help reproduce this issue.

### Summary

This is a bug [reported in the forum](https://discuss.streamlit.io/t/using-st-data-editor-on-dataframes-that-have-styled-columns/44752). The underlying issue is how the data editor determines equivalency of pandas stylers.

If you have a dataframe with two columns (A and B) with column B both styled and disabled, the data editor will correctly show the styled column B and editable column A. However, the widget will not be stateful unless the styler object passed to it is coming from session state. It needs to be identically the same object in memory for the data editor to be stateful. Redefining the same styler element will be viewed as "different" and cause the data editor to be recreated from scratch.

Thanks to DGR from the forum for an executable code snippet. I've modified it here to highlight the issue:

### Reproducible Code Example

```Python
import streamlit as st
import pandas as pd

df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [1, 2, 3, 4]})
styled_df = df.style.format({"B": "{:.2f}€"})

if 'df' not in st.session_state:
    st.session_state.df = df
    st.session_state.styled_df = styled_df

col1, col2 = st.columns(2)

with col1:
    st.subheader("Styled df in session state")
    st.data_editor(
        st.session_state.styled_df,
        key="display",
        disabled=("B"),
    )

with col2:
    st.subheader("Styled df redefined each rerun")
    st.data_editor(
        styled_df,
        key="display2",
        disabled=("B"),
    )

st.write(st.session_state.styled_df == styled_df)
st.write("Streamlit version: ", st.__version__)
```


### Steps To Reproduce

1. Change a value in column A on the left.
2. See it updates correctly.
3. Change a value in column A on the right.
4. See the widget reset and not update correctly.

### Expected Behavior

Two equivalently defined styler objects should be viewed as "the same" for the purposes of the widget reconnecting to an instance on the front end. The data editor should be stateful.

### Current Behavior

The data editor is not stateful when passed equivalent styler objects between reruns; it requires identical pointers to the same styler object.

### Is this a regression?

- [ ] Yes, this used to work in a previous version.

### Debug info

- Streamlit version: 1.23.1
- Python version: 3.11
- Pandas version: 2.0.2
- Operating System: Windows 10
- Browser: Edge
- Virtual environment: Conda


### Additional Information

@LukasMasuch 

### Are you willing to submit a PR?

- [ ] Yes, I am willing to submit a PR!

Changed files:
lib/tests/streamlit/elements/data_editor_test.py, lib/streamlit/elements/data_editor.py

diff --git a/lib/streamlit/elements/data_editor.py b/lib/streamlit/elements/data_editor.py
index 36bad8594..11e79ac97 100644
--- a/lib/streamlit/elements/data_editor.py
+++ b/lib/streamlit/elements/data_editor.py
@@ -66,6 +66,7 @@ from streamlit.runtime.state import (
     register_widget,
 )
 from streamlit.type_util import DataFormat, DataFrameGenericAlias, Key, is_type, to_key
+from streamlit.util import calc_md5
 
 if TYPE_CHECKING:
     import numpy as np
@@ -811,9 +812,17 @@ class DataEditorMixin:
 
         if type_util.is_pandas_styler(data):
             # Pandas styler will only work for non-editable/disabled columns.
-            delta_path = self.dg._get_delta_path_str()
-            default_uuid = str(hash(delta_path))
-            marshall_styler(proto, data, default_uuid)
+            # Get first 10 chars of md5 hash of the key or delta path as styler uuid
+            # and set it as styler uuid.
+            # We are only using the first 10 chars to keep the uuid short since
+            # it will be used for all the cells in the dataframe. Therefore, this
+            # might have a significant impact on the message size. 10 chars
+            # should be good enough to avoid  potential collisions in this case.
+            # Even on collisions, there should not be a big issue with the
+            # rendering in the data editor.
+            styler_uuid = calc_md5(key or self.dg._get_delta_path_str())[:10]
+            data.set_uuid(styler_uuid)
+            marshall_styler(proto, data, styler_uuid)
 
         proto.data = type_util.pyarrow_table_to_bytes(arrow_table)
 
diff --git a/lib/tests/streamlit/elements/data_editor_test.py b/lib/tests/streamlit/elements/data_editor_test.py
index 22944ce99..772615d21 100644
--- a/lib/tests/streamlit/elements/data_editor_test.py
+++ b/lib/tests/streamlit/elements/data_editor_test.py
@@ -565,14 +565,27 @@ class DataEditorTest(DeltaGeneratorTestCase):
             data=np.arange(0, 6, 1).reshape(2, 3),
         )
         styler = df.style
-        # NOTE: If UUID is not set - a random UUID will be generated.
-        styler.set_uuid("FAKE_UUID")
         styler.highlight_max(axis=None)
-        st.data_editor(styler)
+        st.data_editor(styler, key="styler_editor")
 
         proto = self.get_delta_from_queue().new_element.arrow_data_frame
         self.assertEqual(
-            proto.styler.styles, "#T_FAKE_UUIDrow1_col2 { background-color: yellow }"
+            proto.styler.styles, "#T_29028a0632row1_col2 { background-color: yellow }"
+        )
+
+        # Check that different delta paths lead to different element ids
+        st.container().data_editor(styler, width=99)
+        # delta path is: [0, 1, 0]
+        proto = self.get_delta_from_queue().new_element.arrow_data_frame
+        self.assertEqual(
+            proto.styler.styles, "#T_e94cd2b42erow1_col2 { background-color: yellow }"
+        )
+
+        st.container().container().data_editor(styler, width=100)
+        # delta path is: [0, 2, 0, 0]
+        proto = self.get_delta_from_queue().new_element.arrow_data_frame
+        self.assertEqual(
+            proto.styler.styles, "#T_9e33af1e69row1_col2 { background-color: yellow }"
         )
 
     def test_duplicate_column_names_raise_exception(self):