1

I am trying to write a function that gets the class when we pass a given method as argument.

For example, if we have

class Hello: NAME = "HELLO TOTO" def method(self) -> int: return 5 @classmethod def cls_method(cls) -> str: return "Hi" class Bonjour(Hello): NOM = "BONJOUR TOTO" def new_method(self) -> int: return 0 

I would get:

  • Hello from the methods Hello().method or Hello().cls_method
  • Bonjour from the methods Bonjour().new_method or Bonjour().cls_method

I searched on SO but could not find any direct answer to my question.

How could I implement such function (in Python 3.6+ if that matters)?

Thanks

3
  • What is your use-case for this? I'm wondering because the desired result is actually part of the argument. You are having Hello and Bonjour already. Commented Sep 4, 2020 at 3:52
  • Wait, hold on - you want a single solution to handle both classmethods and ordinary methods? Why? You would, AFAICT, be using the technique to implement individual methods, so you already know whether you are working within a classmethod or an ordinary method. Within the classmethod, the passed-in cls is already the class object you need; within the ordinary method, you can check self.__class__. Commented Sep 4, 2020 at 4:12
  • @KarlKnechtel yes, I want a single solution (although I actually only use ordinary methods). The question was not to get the class from the method itself, but rather passing this method to a function that would get the class. Commented Sep 7, 2020 at 3:49

2 Answers 2

2

I believe there's no fool-proof way, but this would work for most cases:

def get_class_of_bound_self(f): assert hasattr(f, '__self__') return f.__self__ if isinstance(f.__self__, type) else type(f.__self__) 

Note that this would break down if f is a method of a metaclass M; it would return M instead of type.

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

3 Comments

thanks... I like your isinstance(self, type)~! :) ... but what is this argument self actually?
@Jean-FrancoisT. I simplified it. Is it clearer now? Or perhaps I do not understand you question.
It was clear before. Just a typo if isinstance(self, type): istead of if isinstance(self_or_cls, type): [missing _or_cls]. And the question was well understood and was solving the problem (although I had actually a solution when posting the question :))
1

I came with the following solution:

import inspect def get_class(func: Callable[..., Any]) -> Any: """Return class of a method. Args: func: callable Returns: Class of the method, if the argument is a method Raises: AttributeError: if the argument is not callable or not a method """ if not callable(func): raise AttributeError(f"{func} shall be callable") if not inspect.ismethod(func): raise AttributeError(f"Callable {func} shall be a method") first_arg = func.__self__ # type: ignore # method have "self" attribute return first_arg if inspect.isclass(first_arg) else first_arg.__class__ 

The last line return first_arg if inspect.isclass(first_arg) else first_arg.__class__ is to handle the cases of class methods (in which case func.__self__ corresponds to cls and is the class itself).

Another alternative without inspect module is with catching exceptions (a big thanks to @Elazar for the idea of using isistance(..., type)):

def get_class(func: Callable[..., Any]) -> Any: """Return class of a method. Args: func: callable Returns: Class of the method, if the argument is a method Raises: AttributeError: if the argument is not callable or not a method """ if not callable(func): raise AttributeError(f"{func} shall be callable") try: first_arg = func.__self__ # type: ignore # method have "self" attribute except AttributeError: raise AttributeError(f"Callable {func} shall be a method") cls_or_type = first_arg.__class__ return first_arg if isinstance(cls_or_type, type) else cls_or_type 

And this is the code I have used to check if you might be interested:

def my_func() -> int: """It feels like a zero""" return 0 for method in [ Hello().method, Bonjour().method, Hello().cls_method, Bonjour().cls_method, Bonjour().new_method, ]: # MyClass = get_class(func) MyClass = get_class_2(method) for attr in ["NAME", "NOM"]: print(f"... {method} - {attr} ...") try: print(getattr(MyClass, attr)) except AttributeError as exp: print(f"Error when getting attribute: {exp}") # class_ = get_class(my_func) for not_method in [my_func, int, Hello]: try: MyClass = get_class(not_method) print(f"{not_method} => NOK (no exception raised)") except AttributeError: print(f"{not_method} => OK") 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.