Are there any functions like the built-in functions getattr
and hasattr
in the standard library but which skip 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
in which the hypothetical functions getclassattr
and hasclassattr
look up the attribute '__get__'
on the class type(self.__func__)
:*
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 here, not hasattr.
if hasclassattr(self.__func__, '__get__'):
# Note that we use getclassattr here, not getattr.
return getclassattr(self.__func__, '__get__')(owner, type(owner))
return types.MethodType(self.__func__, owner)
@property
def __isabstractmethod__(self):
return hasattr(self.__func__, '__isabstractmethod__')
class M(type):
pass
class C(metaclass=M):
def __get__(self, instance, owner=None):
pass
assert ClassMethod(C).__get__(123) == classmethod(C).__get__(123)
* Note that calling the hypothetical functions getclassattr
and hasclassattr
on self.__func__
instead of calling the built-in functions getattr
and hasattr
on it does not work since they look up the attribute '__get__'
both on the instance self.__func__
and on the class type(self.__func__)
:
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):
pass
class C(metaclass=M):
def __get__(self, instance, owner=None):
pass
assert ClassMethod(C).__get__(123) != classmethod(C).__get__(123)
Calling the built-in functions getattr
and hasattr
on type(self.__func__)
does not work either since they look up the attribute '__get__'
both on the instance type(self.__func__)
and on the class type(type(self.__func__))
:
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(type(self.__func__), '__get__'):
return getattr(type(self.__func__), '__get__')(owner, type(owner))
return types.MethodType(self.__func__, owner)
@property
def __isabstractmethod__(self):
return hasattr(self.__func__, '__isabstractmethod__')
class MM(type):
def __get__(self, instance, owner=None):
pass
class M(type, metaclass=MM):
pass
class C(metaclass=M):
pass
assert ClassMethod(C).__get__(123) != classmethod(C).__get__(123)
Instead of introducing new functions getclassattr
and hasclassattr
to skip 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 skip
) that overrides the method __getattribute__
. I think it is 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 skip:
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'
assert getattr(skip(a), 'x') == 'class'
assert getattr(A, 'x') == 'class'
assert getattr(skip(A), 'x') == 'metaclass'