Created
March 1, 2022 18:40
-
-
Save treyhunner/75670b9e315f8993254844cbe88de363 to your computer and use it in GitHub Desktop.
The built-in super function, re-implemented
This file contains hidden or 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 inspect | |
_SENTINEL = object() | |
class super: | |
"""The built-in super "function" re-implemented.""" | |
def __new__(cls, type=_SENTINEL, obj=None): | |
# super() or super(MyClass), but not super(MyClass, my_instance) | |
if type is _SENTINEL or obj is None: | |
frame = inspect.stack()[1].frame | |
# super() with no args checks __class__ slot in outer frame | |
if type is _SENTINEL: | |
type = frame.f_locals["__class__"] | |
# When no "self" specified, get first argument from outer frame | |
if obj is None: | |
first_arg = inspect.getargvalues(frame).args[0] | |
obj = frame.f_locals[first_arg] | |
# The built-in super object exposes these 3 dunder attributes below | |
self = object.__new__(cls) | |
self.__thisclass__ = type | |
self.__self__ = obj | |
if isinstance(obj, type): | |
self.__self_class__ = obj.__class__ | |
elif issubclass(obj, type): | |
self.__self_class__ = obj | |
else: | |
raise TypeError( | |
"super(type, obj): obj must be an instance or subtype of type" | |
) | |
return self | |
def __getattribute__(self, attr): | |
# Looking up self.__attr_name__ would result in infinite recursion | |
this = object.__getattribute__(self, "__thisclass__") | |
self_class = object.__getattribute__(self, "__self_class__") | |
obj = object.__getattribute__(self, "__self__") | |
# Get the method resolution order from __thisclass__ onward | |
this_mro_index = self_class.__mro__.index(this) | |
method_resolution_order = self_class.__mro__[this_mro_index+1:] | |
# Find method/attribute in first class after ours that has a match | |
for cls in method_resolution_order: | |
if attr in cls.__dict__: | |
value = cls.__dict__[attr] | |
# this is how self-binding works (methods are descriptors) | |
if hasattr(value, "__get__"): | |
value = value.__get__(obj) | |
return value | |
# Should raise an attribute error | |
raise object.__getattribute__(self, attr) | |
def __repr__(self): | |
this = object.__getattribute__(self, "__thisclass__") | |
self_class = object.__getattribute__(self, "__self_class__") | |
return f"<super: {this}, <{self_class.__name__} object>>" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment