pythonpython-decorators

Why does inspect identify decorated methods as functions instead of methods in Python?


I'm working on a Python project where I'm using decorators to wrap functions and methods with some logic. However, I've encountered an issue where inspect identifies the decorated methods as functions instead of methods.

Here's a simplified version of my code:

from functools import update_wrapper, wraps
import inspect
from typing import Any, Callable, Optional


def my_decorator(func: Optional[Callable] = None, **decorator_kwargs):
    def decorate(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if inspect.isfunction(func):
                print(f"{func.__name__} is a function")
            elif inspect.ismethod(func):
                print(f"{func.__name__} is a method")
            else:
                raise Exception(f"{func.__name__} is neither a function nor a method")
            return func(*args, **kwargs)
        return wrapper
    if func:
        update_wrapper(decorate, func)
        return decorate(func)
    return decorate

class TestClass:
    def __init__(self, value: Any):
        self.value = value
    
    @my_decorator(foo="bar")
    def print_value(self, second_value: str):
        print(f"Printing Value {self.value}, {second_value}")
        return self.value

def main():
    test_class = TestClass(45)
    test_class.print_value("spam")

if __name__ == "__main__":
    main()

In this code, I've used a decorator to wrap the print_value method of the TestClass class. Despite using functools.wraps to preserve the original method's metadata, inspect still identifies print_value as a function.

How can I correctly identify decorated methods as methods using the inspect module? Is there a specific approach or modification needed to ensure the correct identification of methods?

Additional Information:


Solution

  • print_value is a function. It's used (via the descriptor protocol) to produce a method object when it is accessed via an instance of TestClass.

    >>> TestClass.print_value
    <function TestClass.print_value at 0x105af0900>
    >>> TestClass(5).print_value
    <bound method TestClass.print_value of <__main__.TestClass object at 0x105c49810>>
    

    Generally speaking, you should avoid trying to treat functions intended as methods differently from other functions.