pythoninheritanceclass-method

Access `self` in classmethod when instance (self) calls classmethod


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).


Solution

  • 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
    

    Demo: https://ideone.com/PmSnUC