Last active
November 30, 2024 09:22
-
-
Save amirsoroush/5f232a1fc7d5a93a244401249fcec83e to your computer and use it in GitHub Desktop.
Super() implementation in pure Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import unittest | |
from inspect import isfunction, ismethod | |
def _get_attr(obj, name): | |
"""Bypass object's normal attribute look up with __getattribute__""" | |
return object.__getattribute__(obj, name) | |
class Super: | |
""" | |
A simulated `super` class which supports non-zero arguments form. | |
super(type) -> unbound super object | |
super(type, obj) -> bound super object; requires isinstance(obj, type) | |
super(type, type2) -> bound super object; requires issubclass(type2, type) | |
""" | |
__slots__ = "__self", "__type" | |
def __init__(self, type_, obj_or_type=None) -> None: | |
if not isinstance(type_, type): | |
raise TypeError("Super() argument 1 must be type.") | |
if obj_or_type is not None: | |
if not ( | |
isinstance(obj_or_type, type_) | |
or (isinstance(obj_or_type, type) and issubclass(obj_or_type, type_)) | |
): | |
raise TypeError( | |
"Super(type, obj): obj must be an instance or subtype of type." | |
) | |
self.__type = type_ | |
self.__self = obj_or_type | |
@property | |
def __thisclass__(self): | |
return _get_attr(self, f"_{type(self).__name__}__type") | |
@property | |
def __self__(self): | |
return _get_attr(self, f"_{type(self).__name__}__self") | |
@property | |
def __self_class__(self): | |
__self__ = _get_attr(self, "__self__") | |
if __self__ is None: | |
return None | |
if isinstance(__self__, type): | |
return __self__ | |
__thisclass__ = _get_attr(self, "__thisclass__") | |
if isinstance(__self__, __thisclass__): | |
return type(_get_attr(self, "__self__")) | |
def __get__(self, instance, owner=None): | |
"""convert unbound super to bound super.""" | |
if _get_attr(self, "__self__") is None and instance is not None: | |
return Super(_get_attr(self, "__thisclass__"), instance) | |
return self | |
def __getattribute__(self, name): | |
# Route to properties | |
if name in ("__thisclass__", "__self__", "__self_class__"): | |
return _get_attr(self, name) | |
__self__ = _get_attr(self, "__self__") | |
if __self__ is None: | |
# This is an unbound Super | |
return _get_attr(self, name) | |
# Delegate to __self__ | |
mro = __self__.__mro__ if isinstance(__self__, type) else type(__self__).__mro__ | |
for cls in mro[mro.index(_get_attr(self, "__thisclass__")) + 1 :]: | |
if name in cls.__dict__: | |
o = cls.__dict__[name] | |
if hasattr(o, "__get__") and not isinstance(__self__, type): | |
return o.__get__(__self__, _get_attr(self, "__thisclass__")) | |
else: | |
return o | |
raise AttributeError( | |
f"'{type(self).__name__}' object has no attribute '{name}'" | |
) | |
def __repr__(self) -> str: | |
type_ = _get_attr(self, "__thisclass__") | |
__self__ = _get_attr(self, "__self__") | |
if __self__: | |
return "<Super: <class '{}'>, <{} object>>".format( | |
type_.__name__, type(__self__).__name__ | |
) | |
return f"<Super: <class '{type_.__name__}'>, NULL>" | |
# ---------------------------------- Some tests ---------------------------------- | |
class TestInstantiation(unittest.TestCase): | |
def setUp(self) -> None: | |
class Person: | |
pass | |
self.C = Person | |
def test_valid_arguments(self): | |
class Another(self.C): | |
pass | |
Super(self.C) | |
Super(self.C, self.C()) | |
Super(self.C, self.C) | |
Super(self.C, Another) | |
Super(self.C, Another()) | |
def test_invalid_arguments(self): | |
class Another: | |
pass | |
class Another2(self.C): | |
pass | |
with self.assertRaises(TypeError): | |
Super(self.C, Another) | |
with self.assertRaises(TypeError): | |
Super(Another2, self.C) | |
with self.assertRaises(TypeError): | |
Super(self.C()) | |
with self.assertRaises(TypeError): | |
Super(self.C, Another()) | |
class TestFunctionality(unittest.TestCase): | |
def test_call(self): | |
class Parent: | |
def fn(self): | |
return 10 | |
class Child(Parent): | |
def fn(self): | |
s = Super(Child, self) | |
return s.fn() | |
self.assertEqual(Child().fn(), 10) | |
def test_properties_bound_and_unbound(self): | |
class B(object): | |
pass | |
class C(B): | |
pass | |
class D(C): | |
pass | |
d = D() | |
# instance-bound syntax | |
bsup = super(C, d) | |
self.assertIs(bsup.__thisclass__, C) | |
self.assertIs(bsup.__self__, d) | |
self.assertIs(bsup.__self_class__, D) | |
# class-bound syntax | |
Bsup = super(C, D) | |
self.assertIs(Bsup.__thisclass__, C) | |
self.assertIs(Bsup.__self__, D) | |
self.assertIs(Bsup.__self_class__, D) | |
# unbound syntax | |
usup = super(C) | |
self.assertIs(usup.__thisclass__, C) | |
self.assertIs(usup.__self__, None) | |
self.assertIs(usup.__self_class__, None) | |
def test_unbound_lookup(self): | |
class Parent: | |
a = 1 | |
class Child(Parent): | |
sup = Super(Parent) | |
self.assertIsNone(sup.__self__) | |
obj = Child() | |
self.assertIsNotNone(obj.sup.__self__) | |
with self.assertRaises(AttributeError): | |
obj.sup.a | |
def test_unbound_get_value(self): | |
class B: | |
a = 1 | |
class C(B): | |
pass | |
class D(C): | |
with self.assertRaises(AttributeError): | |
Super(C).a | |
sup = Super(C) | |
self.assertEqual(D().sup.a, 1) | |
def test_return_function_or_method(self): | |
class B1: | |
def f(self): | |
pass | |
class C1(B1): | |
pass | |
s1 = Super(C1, C1) | |
s2 = Super(C1, C1()) | |
self.assertTrue(isfunction(s1.f)) | |
self.assertTrue(ismethod(s2.f)) | |
def test_return_grandparent(self): | |
class GrandParent: | |
def fn(self): | |
return 1 | |
class Parent(GrandParent): | |
def fn(self): | |
return 2 | |
class Child(Parent): | |
def fn(self): | |
return Super(Parent, self).fn() | |
self.assertEqual(Child().fn(), 1) | |
def test_dunder_doc_and_dunder_name(self): | |
class B: | |
"This is class B" | |
class C(B): | |
pass | |
self.assertEqual(Super(C, C).__doc__, B.__doc__) | |
with self.assertRaises(AttributeError): | |
Super(C, C).__name__ | |
def test_metaclass_attribute(self): | |
class M(type): | |
a = 1 | |
class B(metaclass=M): | |
pass | |
class C(B): | |
pass | |
with self.assertRaises(AttributeError): | |
super(C, C).a | |
if __name__ == "__main__": | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a re-implementation of the builtin
super
class in pure Python(a bit more complete that what Guido showed us in this article.super
is a class which returns a proxy object (not the superclass). Its behavior is kind of less obvious especially for beginners.This
Super
(with capital 'S') class lacks the ability to be called without arguments, just like with Python before 3.0 where you had to explicitly specify arguments. The reason for that is in order to successfully call super with zero argument, Python compiler has to do some magic behind the scene and it would get so complicated to implement and eventually diverge from the actual point of this gist.I tried my best to take all possible scenarios into account so that
Super
acts exactly like builtinsuper
. There are some tests at the end.Of-course this is for educational purpose only.
Hope it clarifies some of its secrets.
You're welcome to fix my mistakes or add something to it.