Repository: modin-project/modin

Issue:
[BUG] TypeError in groupby with MODIN_EXPERIMENTAL=True
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Linux Ubuntu 19.04
- **Modin installed from (source or binary)**: source
- **Modin version**: master
- **Python version**: 3.8.3
- **Exact command to reproduce**: python test.py

Test code:
```
import os

os.environ["MODIN_EXPERIMENTAL"] = "True"
os.environ["MODIN_ENGINE"] = "ray"

import modin.pandas as pd

data = {
        "a": [1, 1, 2, 2],
        "b": [11, 21, 12, 11],
    }

df = pd.DataFrame(data)
ref = df.groupby("b").size()
print(ref)
```

Result:
```
Traceback (most recent call last):
  File "2.py", line 15, in <module>
    ref = df.groupby("b").size()
  File "/localdisk/ilyaenko/modin/modin/pandas/groupby.py", line 420, in size
    work_object = SeriesGroupBy(
TypeError: type.__new__(SeriesGroupBy): SeriesGroupBy is not a subtype of type
```



Discussion:

Resolving patch:
diff --git a/modin/experimental/cloud/meta_magic.py b/modin/experimental/cloud/meta_magic.py
--- a/modin/experimental/cloud/meta_magic.py
+++ b/modin/experimental/cloud/meta_magic.py
@@ -17,7 +17,18 @@
 
 from modin import execution_engine
 
-_LOCAL_ATTRS = frozenset(("__new__", "__dict__", "__wrapper_remote__"))
+# the attributes that must be alwasy taken from a local part of dual-nature class,
+# never going to remote end
+_LOCAL_ATTRS = frozenset(
+    (
+        "__new__",
+        "__dict__",
+        "__wrapper_remote__",
+        "__real_cls__",
+        "__mro__",
+        "__class__",
+    )
+)
 
 
 class RemoteMeta(type):
@@ -46,47 +57,44 @@ def __signature__(self):
     def __getattribute__(self, name):
         if name in _LOCAL_ATTRS:
             # never proxy special attributes, always get them from the class type
-            res = object.__getattribute__(self, name)
+            return super().__getattribute__(name)
         else:
             try:
                 # Go for proxying class-level attributes first;
                 # make sure to check for attribute in self.__dict__ to get the class-level
                 # attribute from the class itself, not from some of its parent classes.
-                # Also note we use object.__getattribute__() to skip any potential
-                # class-level __getattr__
-                res = object.__getattribute__(self, "__dict__")[name]
+                res = super().__getattribute__("__dict__")[name]
             except KeyError:
+                # Class-level attribute not found in the class itself; it might be present
+                # in its parents, but we must first see if we should go to a remote
+                # end, because in "remote context" local attributes are only those which
+                # are explicitly allowed by being defined in the class itself.
+                frame = sys._getframe()
                 try:
-                    res = object.__getattribute__(self, name)
+                    is_inspect = frame.f_back.f_code.co_filename == inspect.__file__
                 except AttributeError:
-                    frame = sys._getframe()
+                    is_inspect = False
+                finally:
+                    del frame
+                if is_inspect:
+                    # be always-local for inspect.* functions
+                    return super().__getattribute__(name)
+                else:
                     try:
-                        is_inspect = frame.f_back.f_code.co_filename == inspect.__file__
+                        remote = self.__real_cls__.__wrapper_remote__
                     except AttributeError:
-                        is_inspect = False
-                    finally:
-                        del frame
-                    if is_inspect:
-                        # be always-local for inspect.* functions
-                        res = super().__getattribute__(name)
-                    else:
-                        try:
-                            remote = object.__getattribute__(
-                                object.__getattribute__(self, "__real_cls__"),
-                                "__wrapper_remote__",
-                            )
-                        except AttributeError:
-                            # running in local mode, fall back
-                            res = super().__getattribute__(name)
-                        else:
-                            res = getattr(remote, name)
-        try:
-            # note that any attribute might be in fact a data descriptor,
-            # account for that
-            getter = res.__get__
-        except AttributeError:
-            return res
-        return getter(None, self)
+                        # running in local mode, fall back
+                        return super().__getattribute__(name)
+                    return getattr(remote, name)
+            else:
+                try:
+                    # note that any attribute might be in fact a data descriptor,
+                    # account for that; we only need it for attributes we get from __dict__[],
+                    # because other cases are handled by super().__getattribute__ for us
+                    getter = res.__get__
+                except AttributeError:
+                    return res
+                return getter(None, self)
 
 
 _KNOWN_DUALS = {}
diff --git a/modin/experimental/cloud/rpyc_proxy.py b/modin/experimental/cloud/rpyc_proxy.py
--- a/modin/experimental/cloud/rpyc_proxy.py
+++ b/modin/experimental/cloud/rpyc_proxy.py
@@ -384,9 +384,6 @@ class Wrapper(override, origin_cls, metaclass=ProxyMeta):
         __name__ = cls_name or origin_cls.__name__
         __wrapper_remote__ = remote_cls
 
-        def __new__(cls, *a, **kw):
-            return override.__new__(cls)
-
         def __init__(self, *a, __remote_end__=None, **kw):
             if __remote_end__ is None:
                 __remote_end__ = remote_cls(*a, **kw)
@@ -420,6 +417,8 @@ def __getattribute__(self, name):
             4) check if type(self).__dict__[name] exists
             5) pass through to remote end
             """
+            if name == "__class__":
+                return object.__getattribute__(self, "__class__")
             dct = object.__getattribute__(self, "__dict__")
             if name == "__dict__":
                 return dct


Test patch: