From de79463bc0168e8dd59c04add455b68b16645f3f Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Thu, 2 Sep 2021 18:21:34 -0400 Subject: [PATCH 1/6] BUG: Fix regression in is_list_like is_list_like() used to use isinstance(obj, abc.Iterable) before #39852 where it was optimized for performance. This resulted in a regression where objects that explicitly declare __iter__ as None are considered "list like" but not instances of abc.Iterable. --- pandas/_libs/lib.pyx | 2 +- pandas/tests/libs/test_lib.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index 76955a7e27679..c9548a7e05fc5 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -1092,7 +1092,7 @@ def is_list_like(obj: object, allow_sets: bool = True) -> bool: cdef inline bint c_is_list_like(object obj, bint allow_sets) except -1: return ( # equiv: `isinstance(obj, abc.Iterable)` - hasattr(obj, "__iter__") and not isinstance(obj, type) + getattr(obj, "__iter__", None) is not None and not isinstance(obj, type) # we do not count strings/unicode/bytes as list-like and not isinstance(obj, (str, bytes)) # exclude zero-dimensional numpy arrays, effectively scalars diff --git a/pandas/tests/libs/test_lib.py b/pandas/tests/libs/test_lib.py index 5b7e90fe16d8f..44c44623e7982 100644 --- a/pandas/tests/libs/test_lib.py +++ b/pandas/tests/libs/test_lib.py @@ -206,3 +206,10 @@ def test_no_default_pickle(): # GH#40397 obj = tm.round_trip_pickle(lib.no_default) assert obj is lib.no_default + + +def test_is_list_like_iter_is_none(): + class NotListLike: + __iter__ = None + + assert not lib.is_list_like(NotListLike()) From 57aa1c587cb9d39aa3470cb64a2d412437151414 Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Thu, 2 Sep 2021 19:57:01 -0400 Subject: [PATCH 2/6] Move test_is_list_like_iter_is_none to test_inference --- pandas/tests/dtypes/test_inference.py | 7 +++++++ pandas/tests/libs/test_lib.py | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 3c798d82b9485..6d0f8932b54e1 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -150,6 +150,13 @@ def foo(): foo() +def test_is_list_like_iter_is_none(): + class NotListLike: + __iter__ = None + + assert not inference.is_list_like(NotListLike()) + + def test_is_sequence(): is_seq = inference.is_sequence assert is_seq((1, 2)) diff --git a/pandas/tests/libs/test_lib.py b/pandas/tests/libs/test_lib.py index 44c44623e7982..5b7e90fe16d8f 100644 --- a/pandas/tests/libs/test_lib.py +++ b/pandas/tests/libs/test_lib.py @@ -206,10 +206,3 @@ def test_no_default_pickle(): # GH#40397 obj = tm.round_trip_pickle(lib.no_default) assert obj is lib.no_default - - -def test_is_list_like_iter_is_none(): - class NotListLike: - __iter__ = None - - assert not lib.is_list_like(NotListLike()) From 0623b83b29cb869b4a1c3ec940f82fcce2f4a735 Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Fri, 3 Sep 2021 23:01:57 -0400 Subject: [PATCH 3/6] Update whatsnew --- doc/source/whatsnew/v1.3.3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.3.3.rst b/doc/source/whatsnew/v1.3.3.rst index b4265c1bc5ddd..a1d207e1989e5 100644 --- a/doc/source/whatsnew/v1.3.3.rst +++ b/doc/source/whatsnew/v1.3.3.rst @@ -19,6 +19,7 @@ Fixed regressions - Fixed regression in :meth:`.GroupBy.agg` incorrectly raising in some cases (:issue:`42390`) - Fixed regression in :meth:`RangeIndex.where` and :meth:`RangeIndex.putmask` raising ``AssertionError`` when result did not represent a :class:`RangeIndex` (:issue:`43240`) - Fixed regression in :meth:`read_parquet` where the ``fastparquet`` engine would not work properly with fastparquet 0.7.0 (:issue:`43075`) +- Fixed regression in :func:`is_list_like` where objects with ``__iter__`` set to ``None`` would be identified as iterable .. --------------------------------------------------------------------------- From 5ff646a0e94a89338c7db3839c75da39a0eaa95e Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Sat, 4 Sep 2021 14:00:03 -0400 Subject: [PATCH 4/6] Add PR number to whatsnew --- doc/source/whatsnew/v1.3.3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.3.rst b/doc/source/whatsnew/v1.3.3.rst index a1d207e1989e5..254225eacc6f6 100644 --- a/doc/source/whatsnew/v1.3.3.rst +++ b/doc/source/whatsnew/v1.3.3.rst @@ -19,7 +19,7 @@ Fixed regressions - Fixed regression in :meth:`.GroupBy.agg` incorrectly raising in some cases (:issue:`42390`) - Fixed regression in :meth:`RangeIndex.where` and :meth:`RangeIndex.putmask` raising ``AssertionError`` when result did not represent a :class:`RangeIndex` (:issue:`43240`) - Fixed regression in :meth:`read_parquet` where the ``fastparquet`` engine would not work properly with fastparquet 0.7.0 (:issue:`43075`) -- Fixed regression in :func:`is_list_like` where objects with ``__iter__`` set to ``None`` would be identified as iterable +- Fixed regression in :func:`is_list_like` where objects with ``__iter__`` set to ``None`` would be identified as iterable (:issue:`43373`) .. --------------------------------------------------------------------------- From 7c229553af7767da2a0f087b36d7a03dbd52edb6 Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Sat, 4 Sep 2021 14:00:42 -0400 Subject: [PATCH 5/6] Add forgotten `__getitem__` implementation to test case --- pandas/tests/dtypes/test_inference.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 6d0f8932b54e1..f416a4ab9b89b 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -152,6 +152,9 @@ def foo(): def test_is_list_like_iter_is_none(): class NotListLike: + def __getitem__(self, item): + return self + __iter__ = None assert not inference.is_list_like(NotListLike()) From d981c47a017954edfc5007957e9cb86f5c59c67b Mon Sep 17 00:00:00 2001 From: Roger Aiudi Date: Sat, 4 Sep 2021 17:04:46 -0400 Subject: [PATCH 6/6] Add comment pointing to PR in test --- pandas/tests/dtypes/test_inference.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index f416a4ab9b89b..64a635707c2ff 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -151,6 +151,8 @@ def foo(): def test_is_list_like_iter_is_none(): + # GH 43373 + # is_list_like was yielding false positives with __iter__ == None class NotListLike: def __getitem__(self, item): return self