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")
HelloandBonjouralready.clsis already the class object you need; within the ordinary method, you can checkself.__class__.