Repository: pandas-dev/pandas

Issue:
BUG: Instantiating DataFrames using dicts with iterators/generators  as values causes errors
Working on removing ``compat.lrange``, I've found that Series will accept iterators and generators as input:

```python
>>> pd.Series(reversed(range(3)))  # Iterator work for Series
0    0
1    1
2    2
dtype: int64
>>>  pd.Series((i for i in range(3)))  # Generators work for Series
0    0
1    1
2    2
dtype: int64
```

If iterators and generators work when instantiating Series, I'd expect that they'd work as dict-values for DataFrames also, but they don't:

```python
>>> pd.DataFrame({'A': reversed(range(3))})  # iterator do not work
TypeError: object of type 'range_iterator' has no len()
>>> pd.DataFrame({'A': (i for i in range(3))})  # generator do not work
TypeError: object of type 'generator' has no len()
>>> pd.DataFrame({'A': range(3)})  # ranges work
   A
0  0
1  1
2  2
```

The docs say that list-likes will work as dict values, and because iterators and generators are iterables and list-like, they should work.

This issue is different than #26342, but the fix is still relatively straight forward.

I can can put up a PR one of the coming days,


Discussion:

Resolving patch:
diff --git a/pandas/core/common.py b/pandas/core/common.py
--- a/pandas/core/common.py
+++ b/pandas/core/common.py
@@ -9,7 +9,7 @@
 from datetime import datetime, timedelta
 from functools import partial
 import inspect
-from typing import Any
+from typing import Any, Iterable, Union
 
 import numpy as np
 
@@ -289,6 +289,15 @@ def maybe_make_list(obj):
     return obj
 
 
+def maybe_iterable_to_list(obj: Union[Iterable, Any]) -> Union[list, Any]:
+    """
+    If obj is Iterable but not list-like, consume into list.
+    """
+    if isinstance(obj, abc.Iterable) and not isinstance(obj, abc.Sized):
+        return list(obj)
+    return obj
+
+
 def is_null_slice(obj):
     """
     We have a null slice.
diff --git a/pandas/core/internals/construction.py b/pandas/core/internals/construction.py
--- a/pandas/core/internals/construction.py
+++ b/pandas/core/internals/construction.py
@@ -197,10 +197,11 @@ def init_dict(data, index, columns, dtype=None):
     else:
         keys = com.dict_keys_to_ordered_list(data)
         columns = data_names = Index(keys)
+        arrays = (com.maybe_iterable_to_list(data[k]) for k in keys)
         # GH#24096 need copy to be deep for datetime64tz case
         # TODO: See if we can avoid these copies
-        arrays = [data[k] if not is_datetime64tz_dtype(data[k]) else
-                  data[k].copy(deep=True) for k in keys]
+        arrays = [arr if not is_datetime64tz_dtype(arr) else
+                  arr.copy(deep=True) for arr in arrays]
     return arrays_to_mgr(arrays, data_names, index, columns, dtype=dtype)
 
 
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -1,7 +1,7 @@
 """
 Data structure for 1-dimensional cross-sectional and time series data
 """
-from collections import OrderedDict, abc
+from collections import OrderedDict
 from io import StringIO
 from shutil import get_terminal_size
 from textwrap import dedent
@@ -220,15 +220,11 @@ def __init__(self, data=None, index=None, dtype=None, name=None,
             elif isinstance(data, (set, frozenset)):
                 raise TypeError("{0!r} type is unordered"
                                 "".format(data.__class__.__name__))
-            # If data is Iterable but not list-like, consume into list.
-            elif (isinstance(data, abc.Iterable) and
-                  not isinstance(data, abc.Sized)):
-                data = list(data)
-            else:
-
+            elif isinstance(data, ABCSparseArray):
                 # handle sparse passed here (and force conversion)
-                if isinstance(data, ABCSparseArray):
-                    data = data.to_dense()
+                data = data.to_dense()
+            else:
+                data = com.maybe_iterable_to_list(data)
 
             if index is None:
                 if not is_list_like(data):


Test patch:
diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py
--- a/pandas/tests/frame/test_constructors.py
+++ b/pandas/tests/frame/test_constructors.py
@@ -533,6 +533,30 @@ def test_constructor_dict_of_tuples(self):
         expected = DataFrame({k: list(v) for k, v in data.items()})
         tm.assert_frame_equal(result, expected, check_dtype=False)
 
+    def test_constructor_dict_of_ranges(self):
+        # GH 26356
+        data = {'a': range(3), 'b': range(3, 6)}
+
+        result = DataFrame(data)
+        expected = DataFrame({'a': [0, 1, 2], 'b': [3, 4, 5]})
+        tm.assert_frame_equal(result, expected)
+
+    def test_constructor_dict_of_iterators(self):
+        # GH 26349
+        data = {'a': iter(range(3)), 'b': reversed(range(3))}
+
+        result = DataFrame(data)
+        expected = DataFrame({'a': [0, 1, 2], 'b': [2, 1, 0]})
+        tm.assert_frame_equal(result, expected)
+
+    def test_constructor_dict_of_generators(self):
+        # GH 26349
+        data = {'a': (i for i in (range(3))),
+                'b': (i for i in reversed(range(3)))}
+        result = DataFrame(data)
+        expected = DataFrame({'a': [0, 1, 2], 'b': [2, 1, 0]})
+        tm.assert_frame_equal(result, expected)
+
     def test_constructor_dict_multiindex(self):
         def check(result, expected):
             return tm.assert_frame_equal(result, expected, check_dtype=True,