From 8048c3c3592156528a7bd6396e3efcd574cc7a22 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sat, 9 Apr 2022 17:30:35 +0800 Subject: [PATCH 1/6] Add failing tests --- pandas/tests/dtypes/test_generic.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py index 0612348297bd0..f71769cf0e323 100644 --- a/pandas/tests/dtypes/test_generic.py +++ b/pandas/tests/dtypes/test_generic.py @@ -1,3 +1,4 @@ +import re from warnings import catch_warnings import numpy as np @@ -49,13 +50,28 @@ class TestABCClasses: @pytest.mark.parametrize("abctype1, inst", abc_pairs) @pytest.mark.parametrize("abctype2, _", abc_pairs) - def test_abc_pairs(self, abctype1, abctype2, inst, _): + def test_abc_pairs_instance_check(self, abctype1, abctype2, inst, _): # GH 38588 if abctype1 == abctype2: assert isinstance(inst, getattr(gt, abctype2)) + assert not isinstance(type(inst), getattr(gt, abctype2)) else: assert not isinstance(inst, getattr(gt, abctype2)) + @pytest.mark.parametrize("abctype1, inst", abc_pairs) + @pytest.mark.parametrize("abctype2, _", abc_pairs) + def test_abc_pairs_subclass_check(self, abctype1, abctype2, inst, _): + # GH 38588 + if abctype1 == abctype2: + assert issubclass(type(inst), getattr(gt, abctype2)) + + with pytest.raises( + TypeError, match=re.escape("issubclass() arg 1 must be a class") + ): + issubclass(inst, getattr(gt, abctype2)) + else: + assert not issubclass(type(inst), getattr(gt, abctype2)) + abc_subclasses = { "ABCIndex": [ abctype From 3d5a3d68ab4e52eae83ddc0f640a1b804fc38071 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sat, 9 Apr 2022 17:34:51 +0800 Subject: [PATCH 2/6] Fix pandas.core.dtypes.generic.create_pandas_abc_type --- pandas/core/dtypes/generic.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pandas/core/dtypes/generic.py b/pandas/core/dtypes/generic.py index d6dbc83934db0..a6634cca12e93 100644 --- a/pandas/core/dtypes/generic.py +++ b/pandas/core/dtypes/generic.py @@ -37,14 +37,25 @@ # define abstract base classes to enable isinstance type checking on our # objects def create_pandas_abc_type(name, attr, comp): + def _check(inst): + return getattr(inst, attr, "_typ") in comp # https://github.com/python/mypy/issues/1006 # error: 'classmethod' used with a non-method @classmethod # type: ignore[misc] - def _check(cls, inst) -> bool: - return getattr(inst, attr, "_typ") in comp + def _instancecheck(cls, inst) -> bool: + return _check(inst) and not isinstance(inst, type) + + @classmethod # type: ignore[misc] + def _subclasscheck(cls, inst) -> bool: + # Raise instead of returning False + # This is consistent with default __subclasscheck__ behavior + if not isinstance(inst, type): + raise TypeError("issubclass() arg 1 must be a class") + + return _check(inst) - dct = {"__instancecheck__": _check, "__subclasscheck__": _check} + dct = {"__instancecheck__": _instancecheck, "__subclasscheck__": _subclasscheck} meta = type("ABCBase", (type,), dct) return meta(name, (), dct) From 26b0c68934fbc3dce1596d1ec9a104051c8761d4 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sat, 9 Apr 2022 17:55:18 +0800 Subject: [PATCH 3/6] Update changelog and add comments --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/tests/dtypes/test_generic.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 4920622a15f3f..410df286febfd 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -498,6 +498,7 @@ Conversion - Bug in :meth:`Series.astype` and :meth:`DataFrame.astype` from floating dtype to unsigned integer dtype failing to raise in the presence of negative values (:issue:`45151`) - Bug in :func:`array` with ``FloatingDtype`` and values containing float-castable strings incorrectly raising (:issue:`45424`) - Bug when comparing string and datetime64ns objects causing ``OverflowError`` exception. (:issue:`45506`) +- Bug in metaclass of generic abstract dtypes causing :meth:`DataFrame.apply` and :meth:`Series.apply` to raise for the built-in function ``type`` Strings ^^^^^^^ diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py index f71769cf0e323..4f73754d2708f 100644 --- a/pandas/tests/dtypes/test_generic.py +++ b/pandas/tests/dtypes/test_generic.py @@ -51,7 +51,7 @@ class TestABCClasses: @pytest.mark.parametrize("abctype1, inst", abc_pairs) @pytest.mark.parametrize("abctype2, _", abc_pairs) def test_abc_pairs_instance_check(self, abctype1, abctype2, inst, _): - # GH 38588 + # GH 38588, 46719 if abctype1 == abctype2: assert isinstance(inst, getattr(gt, abctype2)) assert not isinstance(type(inst), getattr(gt, abctype2)) @@ -61,7 +61,7 @@ def test_abc_pairs_instance_check(self, abctype1, abctype2, inst, _): @pytest.mark.parametrize("abctype1, inst", abc_pairs) @pytest.mark.parametrize("abctype2, _", abc_pairs) def test_abc_pairs_subclass_check(self, abctype1, abctype2, inst, _): - # GH 38588 + # GH 38588, 46719 if abctype1 == abctype2: assert issubclass(type(inst), getattr(gt, abctype2)) From ef6425c325e9dd6093625655b000b738913dcde3 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sat, 9 Apr 2022 18:04:27 +0800 Subject: [PATCH 4/6] Update changelog again --- doc/source/whatsnew/v1.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 410df286febfd..41ec9594bdb68 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -498,7 +498,7 @@ Conversion - Bug in :meth:`Series.astype` and :meth:`DataFrame.astype` from floating dtype to unsigned integer dtype failing to raise in the presence of negative values (:issue:`45151`) - Bug in :func:`array` with ``FloatingDtype`` and values containing float-castable strings incorrectly raising (:issue:`45424`) - Bug when comparing string and datetime64ns objects causing ``OverflowError`` exception. (:issue:`45506`) -- Bug in metaclass of generic abstract dtypes causing :meth:`DataFrame.apply` and :meth:`Series.apply` to raise for the built-in function ``type`` +- Bug in metaclass of generic abstract dtypes causing :meth:`DataFrame.apply` and :meth:`Series.apply` to raise for the built-in function ``type`` (:issue:`46684`) Strings ^^^^^^^ From b5e853942cb10583d88b7a0bac44d9d7840abea8 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sun, 10 Apr 2022 12:57:39 +0800 Subject: [PATCH 5/6] Add tests for DataFrame.apply and Series.apply --- pandas/tests/apply/test_frame_apply.py | 26 +++++++++++++++++++++++++ pandas/tests/apply/test_series_apply.py | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/pandas/tests/apply/test_frame_apply.py b/pandas/tests/apply/test_frame_apply.py index 98872571ae2bb..cbedeb79013a4 100644 --- a/pandas/tests/apply/test_frame_apply.py +++ b/pandas/tests/apply/test_frame_apply.py @@ -1551,3 +1551,29 @@ def foo(x): df = DataFrame({"a": [1, 2, 3]}) with tm.assert_produces_warning(UserWarning, match="Hello, World!"): df.agg([foo]) + + +def test_apply_type(): + # GH 46719 + df = DataFrame( + {"col1": [3, "string", float], "col2": [0.25, datetime(2020, 1, 1), np.nan]}, + index=["a", "b", "c"], + ) + + # applymap + result = df.applymap(type) + expected = DataFrame( + {"col1": [int, str, type], "col2": [float, datetime, float]}, + index=["a", "b", "c"], + ) + tm.assert_frame_equal(result, expected) + + # axis 0 + result = df.apply(type, axis=0) + expected = Series({"col1": Series, "col2": Series}) + tm.assert_series_equal(result, expected) + + # axis 1 + result = df.apply(type, axis=1) + expected = Series({"a": Series, "b": Series, "c": Series}) + tm.assert_series_equal(result, expected) diff --git a/pandas/tests/apply/test_series_apply.py b/pandas/tests/apply/test_series_apply.py index ccf84063d67d0..69f7bebb63986 100644 --- a/pandas/tests/apply/test_series_apply.py +++ b/pandas/tests/apply/test_series_apply.py @@ -889,3 +889,11 @@ def test_apply_retains_column_name(): index=Index(range(3), name="x"), ) tm.assert_frame_equal(result, expected) + + +def test_apply_type(): + # GH 46719 + s = Series([3, "string", float], index=["a", "b", "c"]) + result = s.apply(type) + expected = Series([int, str, type], index=["a", "b", "c"]) + tm.assert_series_equal(result, expected) From fd59072af82105fc83f2fe463320c42602e18df1 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sun, 10 Apr 2022 15:23:53 +0800 Subject: [PATCH 6/6] Retrigger timedout test --- pandas/tests/apply/test_frame_apply.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/apply/test_frame_apply.py b/pandas/tests/apply/test_frame_apply.py index cbedeb79013a4..ef7ab4a469865 100644 --- a/pandas/tests/apply/test_frame_apply.py +++ b/pandas/tests/apply/test_frame_apply.py @@ -1568,12 +1568,12 @@ def test_apply_type(): ) tm.assert_frame_equal(result, expected) - # axis 0 + # axis=0 result = df.apply(type, axis=0) expected = Series({"col1": Series, "col2": Series}) tm.assert_series_equal(result, expected) - # axis 1 + # axis=1 result = df.apply(type, axis=1) expected = Series({"a": Series, "b": Series, "c": Series}) tm.assert_series_equal(result, expected)