Repository: holoviz/holoviews

Issue:
Overlay does not detect iterables properly
#### ALL software version info
```
python: 3.8
hv: 1.14.9
bokeh: 2.4.3
```

#### Description of expected behavior and the observed behavior
``hv.Overlay()`` raises exception for some iterable input. Following good practices, it should not discriminate on the type directly (other than on types holoviews itself provides), and use something like `isinstance(x, collections.abc.Iterable)` if need be. 

#### Complete, minimal, self-contained example code that reproduces the issue

```
import holoviews as hv
hv.extension('bokeh')

c1 = hv.Curve([0, 1])
c2 = hv.Curve([10, 20])

# So far so good
hv.Overlay([c1, c2])

# Boom
hv.Overlay({'a': c1, 'b': c2}.values())
```

#### Stack traceback and/or browser JavaScript console output

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [32], in <cell line: 1>()
----> 1 hv.Overlay({'a': c1, 'b': c2}.values())

File ~/Work/lisa/lisa/.lisa-venv-3.8/lib/python3.8/site-packages/holoviews/core/overlay.py:152, in Overlay.__init__(self, items, group, label, **params)
    150 self.__dict__['_group'] = group
    151 self.__dict__['_label'] = label
--> 152 super(Overlay, self).__init__(items, **params)

File ~/Work/lisa/lisa/.lisa-venv-3.8/lib/python3.8/site-packages/holoviews/core/dimension.py:1352, in ViewableTree.__init__(self, items, identifier, parent, **kwargs)
   1350 def __init__(self, items=None, identifier=None, parent=None, **kwargs):
   1351     if items and all(isinstance(item, Dimensioned) for item in items):
-> 1352         items = self._process_items(items)
   1353     params = {p: kwargs.pop(p) for p in list(self.param)+['id', 'plot_id'] if p in kwargs}
   1355     AttrTree.__init__(self, items, identifier, parent, **kwargs)

File ~/Work/lisa/lisa/.lisa-venv-3.8/lib/python3.8/site-packages/holoviews/core/dimension.py:1378, in ViewableTree._process_items(cls, vals)
   1376 items = []
   1377 counts = defaultdict(lambda: 1)
-> 1378 cls._unpack_paths(vals, items, counts)
   1379 items = cls._deduplicate_items(items)
   1380 return items

File ~/Work/lisa/lisa/.lisa-venv-3.8/lib/python3.8/site-packages/holoviews/core/dimension.py:1430, in ViewableTree._unpack_paths(cls, objs, items, counts)
   1428     continue
   1429 new = path is None or len(path) == 1
-> 1430 path = util.get_path(item) if new else path
   1431 new_path = util.make_path_unique(path, counts, new)
   1432 items.append((new_path, obj))

File ~/Work/lisa/lisa/.lisa-venv-3.8/lib/python3.8/site-packages/holoviews/core/util.py:1846, in get_path(item)
   1844         path = path[:1]
   1845 else:
-> 1846     path = (item.group, item.label) if item.label else (item.group,)
   1847 return tuple(capitalize(fn(p)) for (p, fn) in zip(path, sanitizers))

AttributeError: 'dict_values' object has no attribute 'label'
```


Discussion:
I can't spot the underlying issue in that traceback, but I can reproduce the problem and I verified that `hv.Overlay(list({'a': c1, 'b': c2}.values()))` works, so indeed it must somehow be about making an assumption true for a list but not true for a `dict_values` object.
If you can see what needs fixing, a PR would be great!
Changing the following function: 
 
https://github.com/holoviz/holoviews/blob/c265cc15d7ea0311b19df0b5cb0c36c4e42395cf/holoviews/core/dimension.py#L1335-L1340

to 

``` python
def _process_items(cls, vals):
    from .layout import AdjointLayout

    if type(vals) is cls:
        return vals.data
    elif isinstance(vals, (AdjointLayout, str)):
        vals = [vals]
    elif isinstance(vals, Iterable):
        vals = list(vals)
```

Makes it possible to use `dict_values` as input and pass the unit tests (on my computer). 
That looks good to me! PR?

Resolving patch:
diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py
--- a/holoviews/core/dimension.py
+++ b/holoviews/core/dimension.py
@@ -11,6 +11,7 @@
 from collections import defaultdict, Counter
 from itertools import chain
 from functools import partial
+from collections.abc import Iterable
 
 import param
 import numpy as np
@@ -1334,10 +1335,16 @@ def __init__(self, items=None, identifier=None, parent=None, **kwargs):
     @classmethod
     def _process_items(cls, vals):
         "Processes list of items assigning unique paths to each."
+        from .layout import AdjointLayout
+
         if type(vals) is cls:
             return vals.data
-        elif not isinstance(vals, (list, tuple)):
+        elif isinstance(vals, (AdjointLayout, str)):
+            # `string` vals isn't supported but checked anyway
+            # for better exception message.
             vals = [vals]
+        elif isinstance(vals, Iterable):
+            vals = list(vals)
         items = []
         counts = defaultdict(lambda: 1)
         cls._unpack_paths(vals, items, counts)


Test patch:
diff --git a/holoviews/tests/plotting/bokeh/test_overlayplot.py b/holoviews/tests/plotting/bokeh/test_overlayplot.py
--- a/holoviews/tests/plotting/bokeh/test_overlayplot.py
+++ b/holoviews/tests/plotting/bokeh/test_overlayplot.py
@@ -273,6 +273,11 @@ def test(x):
         _, vline_plot = plot.subplots.values()
         assert vline_plot.handles['glyph'].location == 1
 
+    def test_overlay_iterable(self):
+        # Related to https://github.com/holoviz/holoviews/issues/5315
+        c1 = Curve([0, 1])
+        c2 = Curve([10, 20])
+        Overlay({'a': c1, 'b': c2}.values())
 
 
 class TestLegends(TestBokehPlot):