kloffy
kloffy

Reputation: 2928

Call unbound method, get class that it was accessed through?

In Python3, instance methods can be called in two ways, obj.ix() or Foo.ix(obj). Setting aside whether it is a good idea or not: When using the latter, is there a way to get the class that the instance method was accessed through?

class Foo(object):
    @classmethod
    def cx(cls, obj):
        print(cls.X)
    def ix(self):
        # Any way to get the class that ix was accessed through?
        print(self.X)

class AFoo(Foo):
    X = "A"

class BFoo(Foo):
    X = "B"


a = AFoo()
AFoo.cx(a)  # Prints "A"
AFoo.ix(a)  # Prints "A"

b = BFoo()
BFoo.cx(b)  # Prints "B"
BFoo.ix(b)  # Prints "B"

AFoo.cx(b)  # Prints "A"
AFoo.ix(b)  # Prints "B" -> I would like "A", like classmethod.

BFoo.cx(a)  # Prints "B"
BFoo.ix(a)  # Prints "A" -> I would like "B", like classmethod.

As you can see, the desired behavior is trivial to achieve with a class method, but there does not appear to be a way to do the same with an instance method.

Upvotes: 1

Views: 132

Answers (2)

kloffy
kloffy

Reputation: 2928

I have already accepted user2357112's answer, but just in case anyone is interested I found another way to do it (based on A class method which behaves differently when called as an instance method?):

import types

class Foo(object):
    @classmethod
    def x(cls, obj):
        print(cls.X)
    def __init__(self):
        self.x = types.MethodType(type(self).x, self)

class AFoo(Foo):
    X = "A"

class BFoo(Foo):
    X = "B"

a = AFoo()
b = BFoo()

a.x()       # Prints "A"
AFoo.x(a)   # Prints "A"
AFoo.x(b)   # Prints "A"

b.x()       # Prints "B"
BFoo.x(b)   # Prints "B"
BFoo.x(a)   # Prints "B"

Upvotes: 1

user2357112
user2357112

Reputation: 282043

Nope. This information is not preserved. If you want that info, you'd have to write a custom descriptor to implement a new method type. For example:

import functools

class CrazyMethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        if instance is None:
            return functools.partial(self.func, owner)
        return functools.partial(self.func, instance, instance)

class Foo:
    @CrazyMethod
    def foo(accessed_through, self):
        print(accessed_through)

class Bar(Foo): pass

obj = Bar()
obj.foo()     # <__main__.Bar object at 0xb727dd4c>
Bar.foo(obj)  # <class '__main__.Bar'>
Foo.foo(obj)  # <class '__main__.Foo'>

Upvotes: 2

Related Questions