'Are there any functions like getattr/hasattr but bypassing instance attributes?

Are there any functions like the built-in functions getattr and hasattr in the standard library but which bypass instance attributes during attribute lookup, like the implicit lookup of special methods?

Let’s call these hypothetical functions getclassattr and hasclassattr. Here are the implementations that I would expect:

null = object()

def getclassattr(obj, name, default=null, /):
    if not isinstance(name, str):
        raise TypeError('getclassattr(): attribute name must be string')
    try:
        classmro = vars(type)['__mro__'].__get__(type(obj))
        for cls in classmro:
            classdict = vars(type)['__dict__'].__get__(cls)
            if name in classdict:
                attr = classdict[name]
                attrclassmro = vars(type)['__mro__'].__get__(type(attr))
                for attrclass in attrclassmro:
                    attrclassdict = vars(type)['__dict__'].__get__(attrclass)
                    if '__get__' in attrclassdict:
                        return attrclassdict['__get__'](attr, obj, type(obj))
                return attr
        classname = vars(type)['__name__'].__get__(type(obj))
        raise AttributeError(f'{classname!r} object has no attribute {name!r}')
    except AttributeError as exc:
        try:
            classmro = vars(type)['__mro__'].__get__(type(obj))
            for cls in classmro:
                classdict = vars(type)['__dict__'].__get__(cls)
                if '__getattr__' in classdict:
                    return classdict['__getattr__'](obj, name)
        except AttributeError as exc_2:
            exc = exc_2
        except BaseException as exc_2:
            raise exc_2 from None
        if default is not null:
            return default
        raise exc from None
def hasclassattr(obj, name, /):
    try:
        getclassattr(obj, name)
    except AttributeError:
        return False
    return True

A use case is a pure Python implementation of the built-in class classmethod:*

import types

class ClassMethod:

    def __init__(self, function):
        self.__func__ = function

    def __get__(self, instance, owner=None):
        if instance is None and owner is None:
            raise TypeError('__get__(None, None) is invalid')
        if owner is None:
            owner = type(instance)
        # Note that we use hasclassattr instead of hasattr here.
        if hasclassattr(self.__func__, '__get__'):
            # Note that we use getclassattr instead of getattr here.
            return getclassattr(self.__func__, '__get__')(owner, type(owner))
        return types.MethodType(self.__func__, owner)

    @property
    def __isabstractmethod__(self):
        return hasattr(self.__func__, '__isabstractmethod__')

* Note that this implementation would not work with the built-in functions getattr and hasattr since they look up in instance attributes first, as this comparison with the built-in class classmethod shows:

>>> import types
>>> class ClassMethod:
...     def __init__(self, function):
...         self.__func__ = function
...     def __get__(self, instance, owner=None):
...         if instance is None and owner is None:
...             raise TypeError('__get__(None, None) is invalid')
...         if owner is None:
...             owner = type(instance)
...         if hasattr(self.__func__, '__get__'):
...             return getattr(self.__func__, '__get__')(owner, type(owner))
...         return types.MethodType(self.__func__, owner)
...     @property
...     def __isabstractmethod__(self):
...         return hasattr(self.__func__, '__isabstractmethod__')
... 
>>> class M(type):
...     def __get__(self, instance, owner=None):
...         return 'metaclass'
... 
>>> class A(metaclass=M):
...     def __get__(self, instance, owner=None):
...         return 'class'
... 
>>> ClassMethod(A).__get__('foo')
'class'
>>> classmethod(A).__get__('foo')
'metaclass'


Solution 1:[1]

Instead of introducing the new functions getclassattr and hasclassattr to bypass instance attributes during attribute lookup, like the implicit lookup of special methods, an alternative approach is to introduce a proxy class (let’s call it bypass) that overrides the method __getattribute__. I think this may be a better approach since the method __getattribute__ is a hook designed for customising attribute lookup, and it works with the built-in functions getattr and hasattr but also with the attribute retrieval operator .:

class bypass:

    def __init__(self, subject):
        self.subject = subject

    def __getattribute__(self, name):
        obj = super().__getattribute__('subject')
        classmro = vars(type)['__mro__'].__get__(type(obj))
        for cls in classmro:
            classdict = vars(type)['__dict__'].__get__(cls)
            if name in classdict:
                attr = classdict[name]
                attrclassmro = vars(type)['__mro__'].__get__(type(attr))
                for attrclass in attrclassmro:
                    attrclassdict = vars(type)['__dict__'].__get__(attrclass)
                    if '__get__' in attrclassdict:
                        return attrclassdict['__get__'](attr, obj, type(obj))
                return attr
        classname = vars(type)['__name__'].__get__(type(obj))
        raise AttributeError(f'{classname!r} object has no attribute {name!r}')

class M(type):
    x = 'metaclass'

class A(metaclass=M):
    x = 'class'

a = A()
a.x = 'object'

assert getattr(a, 'x') == 'object' and getattr(bypass(a), 'x') == 'class'
assert getattr(A, 'x') == 'class' and getattr(bypass(A), 'x') == 'metaclass'

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1