From 63a91d0992b5f2837dc028d1bab34e659535b6b4 Mon Sep 17 00:00:00 2001
From: MeeseeksMachine <39504233+meeseeksmachine@users.noreply.github.com>
Date: Wed, 16 Nov 2022 10:59:43 +0100
Subject: [PATCH] Backport PR #49676 on branch 1.5.x (REGR: Remove groupby's
 __getattribute__ for nth) (#49705)

* Backport PR #49676: REGR: Remove groupby's __getattribute__ for nth

Co-authored-by: Richard Shadrach <45562402+rhshadrach@users.noreply.github.com>
---
 doc/source/whatsnew/v1.5.2.rst  |  1 +
 pandas/core/groupby/groupby.py  | 27 ++++++++++++---------------
 pandas/core/groupby/indexing.py |  4 ++--
 3 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst
index 446235d1656dc..540ca2b12165c 100644
--- a/doc/source/whatsnew/v1.5.2.rst
+++ b/doc/source/whatsnew/v1.5.2.rst
@@ -18,6 +18,7 @@ Fixed regressions
 - Fixed regression in :meth:`DataFrame.plot` preventing :class:`~matplotlib.colors.Colormap` instance
   from being passed using the ``colormap`` argument if Matplotlib 3.6+ is used (:issue:`49374`)
 - Fixed regression in :func:`date_range` returning an invalid set of periods for ``CustomBusinessDay`` frequency and ``start`` date with timezone (:issue:`49441`)
+- Fixed performance regression in groupby operations (:issue:`49676`)
 -
 
 .. ---------------------------------------------------------------------------
diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py
index 66c459b90a999..98df2423614d3 100644
--- a/pandas/core/groupby/groupby.py
+++ b/pandas/core/groupby/groupby.py
@@ -982,15 +982,6 @@ def __getattr__(self, attr: str):
             f"'{type(self).__name__}' object has no attribute '{attr}'"
         )
 
-    def __getattribute__(self, attr: str):
-        # Intercept nth to allow both call and index
-        if attr == "nth":
-            return GroupByNthSelector(self)
-        elif attr == "nth_actual":
-            return super().__getattribute__("nth")
-        else:
-            return super().__getattribute__(attr)
-
     @final
     def _make_wrapper(self, name: str) -> Callable:
         assert name in self._apply_allowlist
@@ -3015,14 +3006,13 @@ def backfill(self, limit=None):
         )
         return self.bfill(limit=limit)
 
-    @final
+    # https://github.com/python/mypy/issues/1362
+    # Mypy does not support decorated properties
+    @final  # type: ignore[misc]
+    @property
     @Substitution(name="groupby")
     @Substitution(see_also=_common_see_also)
-    def nth(
-        self,
-        n: PositionalIndexer | tuple,
-        dropna: Literal["any", "all", None] = None,
-    ) -> NDFrameT:
+    def nth(self) -> GroupByNthSelector:
         """
         Take the nth row from each group if n is an int, otherwise a subset of rows.
 
@@ -3125,6 +3115,13 @@ def nth(
         1  1  2.0
         4  2  5.0
         """
+        return GroupByNthSelector(self)
+
+    def _nth(
+        self,
+        n: PositionalIndexer | tuple,
+        dropna: Literal["any", "all", None] = None,
+    ) -> NDFrameT:
         if not dropna:
             with self._group_selection_context():
                 mask = self._make_mask_from_positional_indexer(n)
diff --git a/pandas/core/groupby/indexing.py b/pandas/core/groupby/indexing.py
index be7b7b3369e89..750097b403f26 100644
--- a/pandas/core/groupby/indexing.py
+++ b/pandas/core/groupby/indexing.py
@@ -297,7 +297,7 @@ def __call__(
         n: PositionalIndexer | tuple,
         dropna: Literal["any", "all", None] = None,
     ) -> DataFrame | Series:
-        return self.groupby_object.nth_actual(n, dropna)
+        return self.groupby_object._nth(n, dropna)
 
     def __getitem__(self, n: PositionalIndexer | tuple) -> DataFrame | Series:
-        return self.groupby_object.nth_actual(n)
+        return self.groupby_object._nth(n)