Repository: spyder-ide/spyder

Issue:
Can't open Dict in variable explorer with mixed data types as keys.
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

1: Create a dictionary with a mixture of strings and numeric values as keys.
eg.
colors = {1: 'red','Y': 'yellow'}

2: Attempt to open the dictionary in the variable explorer.

3: no profit.

EDIT:
Using this function:
```
def mixs(self, num):
    try:
        ele = int(num)
        return (0, ele, '')
    except ValueError:
        return (1, num, '')
```
 as a key for the sorted() here:
```
File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
```
fixes the problem, but probably not in the best way.

### Traceback
```python-traceback
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
Traceback (most recent call last):
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionsdelegate.py", line 160, in createEditor
    readonly=readonly)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1310, in setup
    remote=remote)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1250, in __init__
    title)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 1117, in __init__
    minmax=minmax)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 143, in __init__
    self.set_data(data)
  File "C:\Users\ifeed\anaconda3\envs\spyder\lib\site-packages\spyder\plugins\variableexplorer\widgets\collectionseditor.py", line 173, in set_data
    self.keys = sorted(list(data.keys()))
TypeError: '<' not supported between instances of 'str' and 'int'
```

## Versions

* Spyder version: 4.1.4 
* Python version: 3.7.7
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 10

### Dependencies

```

# Mandatory:
atomicwrites >=1.2.0           :  1.4.0 (OK)
chardet >=2.0.0                :  3.0.4 (OK)
cloudpickle >=0.5.0            :  1.4.1 (OK)
diff_match_patch >=20181111    :  20181111 (OK)
intervaltree                   :  None (OK)
IPython >=4.0                  :  7.16.1 (OK)
jedi =0.17.1                   :  0.17.1 (OK)
nbconvert >=4.0                :  5.6.1 (OK)
numpydoc >=0.6.0               :  1.0.0 (OK)
paramiko >=2.4.0               :  2.7.1 (OK)
parso =0.7.0                   :  0.7.0 (OK)
pexpect >=4.4.0                :  4.8.0 (OK)
pickleshare >=0.4              :  0.7.5 (OK)
psutil >=5.3                   :  5.7.0 (OK)
pygments >=2.0                 :  2.6.1 (OK)
pylint >=1.0                   :  2.4.4 (OK)
pyls >=0.34.0;<1.0.0           :  0.34.1 (OK)
qdarkstyle >=2.8               :  2.8.1 (OK)
qtawesome >=0.5.7              :  0.7.2 (OK)
qtconsole >=4.6.0              :  4.7.5 (OK)
qtpy >=1.5.0                   :  1.9.0 (OK)
rtree >=0.8.3                  :  0.9.4 (OK)
sphinx >=0.6.6                 :  3.1.1 (OK)
spyder_kernels >=1.9.2;<1.10.0 :  1.9.2 (OK)
watchdog                       :  None (OK)
zmq >=17                       :  19.0.1 (OK)

# Optional:
cython >=0.21                  :  None (OK)
matplotlib >=2.0.0             :  None (OK)
numpy >=1.7                    :  None (OK)
pandas >=0.13.1                :  None (OK)
scipy >=0.17.0                 :  None (OK)
sympy >=0.7.3                  :  None (OK)
```



Discussion:
Hi @catcallofcthulhu thanks for the feedback and thanks for taking the time check for a solution! :tada: 

If you want you can help us fixing this. I would say that taking a look to our [contributing guide](https://github.com/spyder-ide/spyder/blob/4.x/CONTRIBUTING.md) could help you help us :) (the fix should go to the 4.x branch).

Also, just in case, what do you think about the proposed solution @ccordoba12?
> lso, just in case, what do you think about the proposed solution @ccordoba12?

I think we can catch the error and use `list(data.keys()` in case sorting fails.
From Python 3.7, the order of a dict is specified by the language, so perhaps we should stop sorting the keys ourselves?

Reference: https://www.blog.pythonlibrary.org/2018/02/27/python-3-7-dictionaries-now-ordered/
@jitseniesen, we still need to sort them to avoid the problem reported on issue #10702.
If a new sorting is implemented, it would be quite useful if it used natural sorting for strings (e.g. "Test 3" before "Test 100"). Is there already being worked on? Else this should be quick to implement, I can do that.
@skjerns, I like that idea! I'll fix this bug first, then you can give it a try in a different pull request.

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/collectionseditor.py b/spyder/plugins/variableexplorer/widgets/collectionseditor.py
--- a/spyder/plugins/variableexplorer/widgets/collectionseditor.py
+++ b/spyder/plugins/variableexplorer/widgets/collectionseditor.py
@@ -170,7 +170,13 @@ def set_data(self, data, coll_filter=None):
             self.title += _("Set")
             self._data = list(data)
         elif isinstance(data, dict):
-            self.keys = sorted(list(data.keys()))
+            try:
+                self.keys = sorted(list(data.keys()))
+            except TypeError:
+                # This is necessary to display dictionaries with mixed
+                # types as keys.
+                # Fixes spyder-ide/spyder#13481
+                self.keys = list(data.keys())
             self.title += _("Dictionary")
             if not self.names:
                 self.header0 = _("Key")


Test patch:
diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py b/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
--- a/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
+++ b/spyder/plugins/variableexplorer/widgets/tests/test_collectioneditor.py
@@ -812,5 +812,17 @@ def test_collectionseditor_when_clicking_on_header_and_large_rows(qtbot):
     assert data(view.model, 0, 0) == 9999
 
 
+def test_dicts_with_mixed_types_as_key(qtbot):
+    """
+    Test that we can show dictionaries with mixed data types as keys.
+
+    This is a regression for spyder-ide/spyder#13481.
+    """
+    colors = {1: 'red', 'Y': 'yellow'}
+    editor = CollectionsEditor()
+    editor.setup(colors)
+    assert editor.widget.editor.source_model.keys == [1, 'Y']
+
+
 if __name__ == "__main__":
     pytest.main()