Repository navigation
BUG: is_list_like was False for an Enum class so it could not be used as columns - #68035
HuzaifaChaudary wants to merge 1 commit into
Conversation
… as columns an Enum subclass is iterable, list(Color) works, but is_list_like said False and DataFrame(columns=Color) raised TypeError. the class check was there because list.__iter__ exists while iter(list) raises. that is only true when the metaclass has no __iter__ of its own, which an Enum does have, so the check now asks the metaclass instead of refusing every class. enum members are unaffected, they stay scalar.
There was a problem hiding this comment.
🟢 Approval recommended
The behavioral change is narrowly targeted, matches Python iteration semantics for classes, and is covered by regression tests plus a whatsnew entry.
Pull request overview
Fixes a regression where Enum subclasses (which are iterable via their metaclass) were incorrectly treated as non-list-like, causing DataFrame(columns=MyEnum) / DataFrame(index=MyEnum) to raise TypeError. The change updates the low-level is_list_like implementation to treat classes as list-like only when their metaclass provides __iter__, matching Python’s actual iter() behavior for class objects.
Changes:
- Adjust
c_is_list_liketo allow iterable classes when their metaclass defines__iter__(e.g.,EnumType). - Add regression tests ensuring
Enumclasses are list-like butEnummembers remain non-list-like scalars. - Document the fix in the v3.1.0 whatsnew notes.
File summaries
| File | Description |
|---|---|
| pandas/tests/frame/test_constructors.py | Adds a regression test that DataFrame(columns=EnumSubclass) constructs with the expected enum-member columns. |
| pandas/tests/dtypes/test_inference.py | Adds focused tests for is_list_like behavior on enum classes vs enum members and on regular classes. |
| pandas/_libs/lib.pyx | Updates core list-likeness detection to consult the metaclass __iter__ when the object is a class. |
| doc/source/whatsnew/v3.1.0.rst | Adds a whatsnew entry describing the bug fix and linking to the issue. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # a class only iterates if its metaclass says so. `list.__iter__` exists but | ||
| # `iter(list)` fails, while an Enum subclass is iterable through EnumType | ||
| and not (isinstance(obj, type) | ||
| and getattr(type(obj), "__iter__", None) is None) |
There was a problem hiding this comment.
how does this impact perf in regular cases? would it be simpler just to check directly for Enum?
There was a problem hiding this comment.
measured both, on this branch built from source against the same tree with only this hunk
reverted. lib.is_list_like, min of 7 runs of 2,000,000 calls, ns per call:
| input | base | this branch | delta | same-build noise |
|---|---|---|---|---|
[1, 2, 3] |
9.32 | 9.26 | -0.06 | +0.25 |
(1, 2, 3) |
54.69 | 56.02 | +1.33 | -1.49 |
{"a": 1} |
53.51 | 54.98 | +1.47 | +0.04 |
{1, 2} |
53.92 | 54.77 | +0.85 | +1.01 |
np.arange(10) |
8.78 | 8.51 | -0.27 | -0.12 |
Series |
107.80 | 108.33 | +0.53 | +0.89 |
Index |
84.34 | 84.53 | +0.19 | +0.44 |
"abc" |
30.48 | 31.18 | +0.70 | +0.49 |
3 |
15.31 | 15.00 | -0.31 | +0.03 |
None |
15.47 | 15.05 | -0.42 | -0.39 |
np.float64(1.5) |
15.31 | 15.38 | +0.07 | -0.01 |
int (a class) |
15.35 | 15.63 | +0.28 | +0.40 |
the last column is the same binary measured twice with nothing changed, and it spans -1.49 to
+1.01, so every delta in the middle column is inside the noise. that matches what the
expression does: for anything that is not a class, isinstance(obj, type) is False and the
added getattr never runs, and that isinstance call was already there on the base. the only
input that does more work than before is a class that has __iter__, which is the int row.
on checking for Enum directly , i built that too:
and not (isinstance(obj, type) and not isinstance(obj, EnumMeta))it needs from enum import EnumMeta in lib.pyx and it is the same speed. the difference is
what it says about anything else with an iterating metaclass:
class IterMeta(type):
def __iter__(cls): return iter((1, 2))
class Registry(metaclass=IterMeta): pass
list(Registry) # [1, 2]
is_list_like(Registry) # EnumMeta version: False. this branch: TrueEnum is not special here, EnumType.__iter__ is. so the direct check fixes the reported
case and still calls a class that iterates a scalar, which is the same shape as the original
bug. the metaclass version is why list and dict stay False: list.__iter__ exists but
type(list) has no __iter__, so iter(list) raises , which is what the
not isinstance(obj, type) guard was protecting against in the first place.
happy to switch to the EnumMeta form if you would rather keep the rule narrow, it is a
two line change and the tests for list/dict/Plain pass either way. 8099 passed, 113
skipped, 6 xfailed across tests/dtypes/, tests/frame/test_constructors.py and
tests/indexes/test_base.py on the version in the diff.
closes #54386
an enum subclass is iterable ,
list(Color)gives you the members , butis_list_likesaidfalse for it so
DataFrame(columns=Color)raised .the class check in
c_is_list_likeis there for a good reason ,list.__iter__exists whileiter(list)raises . but that only holds when the metaclass has no__iter__of its own . anenum has one through
EnumType, which is whyiter(Color)works . so it asks the metaclassnow instead of turning down every class .
worth flagging , the shorter version of this does not work . checking
type(obj)looks rightbut
getattron a type still walks the metaclass , soColor.REDcomes back list like , anda member is a scalar not a collection . there is a test pinning that .
i compared the old check , the new one and what
iter()really does over 45 objects . fouranswers change and all four were wrong before , enum classes and anything else with an
iterable metaclass . nothing else moves .
89905 tests pass across dtypes , frame , series , indexes , reshape , groupby , apply ,
indexing , tools and base . both new tests fail with the change taken out .
disclosure , as the automated contributions policy asks for . tool is claude code , model
claude opus 5 . i used it to work through the cause , to write the patch and the tests , and
to help word this description . i went over the result myself and every number quoted above
comes from a run on my own machine . sorry it was not in here when i opened it , i had not
read the policy at that point .