Does function know about the class before binding

54 Views Asked by At

Is there a way to access a class (where function is defined as a method) before there is an instance of that class?

class MyClass:
    def method(self):
        print("Calling me")

m1 = MyClass.method

instance = MyClass()
m2 = instance.method
print(m2.__self__.__class__)  # <class 'MyClass'>

# how to access `MyClass` from `m1`?

For example I have m1 variable somewhere in my code and want to have a reference to MyClass the same way I can access it from bound method m2.__self__.__class__.

print(m1.__qualname__)  # 'MyClass.method'

The only option I was able to find is __qualname__ which is a string containing name of the class.

2

There are 2 best solutions below

0
On BEST ANSWER

The attribute __self__ itself is annotated by Python when the function is bound to an instance and become a method. (The code to that is run somewhere when running the __get__ code in the function, but passing an instance different than None).

So, as people pointed out, you have the option of getting the classname as a string by going through __qualname__. Otherwise, if the functions/methods for which you will need this feature are known beforehand, it is possible to create a decorator that will annotate their class when they are retrieved as a class attribute (in contrast to the native annotation which only takes place when retrieving then as an instance attribute):

class unboundmethod:
    def __init__(self, func, cls):
        self.__func__ = func
        self.class_ = cls
        self.__self__ = None
        
    def __call__(self, instance, *args, **kw):
        if not isinstance(instance, self.class_):
            # This check is actually optional fancy stuff, since we are here! :-)
            raise TypeError(f"First parameter fo {self.__func__.__name__} must be an instance of {self.class_}")
        return self.__func__(instance, *args, **kw)
        
    def __repr__(self):
        return f"Unbound method {self.__func__!r} related to {self.class_}"


class clsbind:
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        if instance is None:
            # the function is being retrieved from the class:
            return unboundmethod(self.func, owner)
        # return control to usual method creation codepath:
        return self.func.__get__(instance, owner)
    

class MyClass:
    @clsbind
    def method(self):
        print("Calling me")

And on the REPL you can have this:


In [136]: m1 = MyClass.method

In [137]: m1.class_
Out[137]: __main__.MyClass

In [138]: m1(MyClass())
Calling me

1
On

You can get the class instance using the __qualname__

    my_class = eval(m1.__qualname__.split('.')[-2])
    print(my_class)

Not the most generic and safest approach, but should work for this simple scenario.