Is it possible to access the object instance in a Python @classmethod annotated function when the call occurs via the instance itself rather than the class?
class Foo(object):
def __init__(self, text):
self.text = text
@classmethod
def bar(cls):
return None
print(Foo.bar())
foo = Foo('foo')
# If possible, I'd like to return "foo"
print(foo.bar())
I'm working around limitations of a library which calls a class method via the instance (rather than the class) and was hoping I could somehow workaround the fact that I can only access the class in bar(cls)
(I'd to access the instance to access .text
). I don't think that's possible, but I thought I'd ask (I couldn't unearth something on the internet either for such a niche request).
One possible approach without modifying Foo
is to define bar
as an instance method of a subclass, but use a custom descriptor to make attribute access to the method return the class method of the same name of the super class instead when the attribute is accessed from the class, when the instance passed to __get__
is None
:
class hybridmethod:
def __init__(self, f):
self.f = f
def __get__(self, obj, cls=None):
if obj is not None:
return self.f.__get__(obj)
return getattr(super(cls, cls), self.f.__name__).__get__(cls)
class MyFoo(Foo):
@hybridmethod
def bar(self):
return self.text
print(MyFoo.bar()) # outputs None
foo = MyFoo('foo')
print(foo.bar()) # outputs foo
Demo: https://ideone.com/yVAzr3
EDIT: Since you have now clarified in the comment that you have full control over Foo
, you can instead modify Foo.bar
directly such that it is a custom descriptor whose __get__
method calls a class method or an instance method based on the aforementioned condition.
For convenience I've made the descriptor a decorator that decorates a class method with an additional instancemethod
method that can decorate an instance method:
class instanceable_classmethod(classmethod):
def __init__(self, func):
super().__init__(func)
self.instance_func = self.__func__
def instancemethod(self, func):
self.instance_func = func
return self
def __get__(self, obj, cls=None):
if obj is None:
return super().__get__(obj, cls)
return self.instance_func.__get__(obj)
class Foo(object):
def __init__(self, text):
self.text = text
@instanceable_classmethod
def bar(cls):
return None
@bar.instancemethod
def bar(self):
return self.text
print(Foo.bar()) # outputs None
foo = Foo('foo')
print(foo.bar()) # outputs foo