1

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

2
  • 4
    Is there anything forcing you to use a classmethod? Does the library you're using actually need this method to be a classmethod? What library is this, anyway? Commented Feb 5 at 2:33
  • See also this related / possible duplicate depending on the setup: Same name for classmethod and instancemethod. It has some great (and even typed) answers Commented Jun 14 at 9:17

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

6 Comments

Nice idea, I didn't know about __get__. Foo is actually under my control (it's not part of the library). It's just that it's sometimes called as class method and sometimes as instance method (which calls the class method as that's how it's defined). I was looking for a way to access the instance when it's called as an instance method. With your suggestion, I could just move __get__ to Foo and decide on the behaviour - is that correct?
Yes that's correct. I've updated my answer with a solution that modifies Foo directly then.
@orange: This is going to collapse on you if that library ever refactors in a way that changes how these methods are called.
@user2357112 That's not a concern - I have test cases preventing this from being unnoticed.
@orange: But even if you notice it collapsing on you, it's still going to collapse on you.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.