0

I want to list the arguments of my methods for a self documenting REST API. I have found that I can get the arguments of a method using:

method.__code__.co_varnames[:method.__code__.co_argcount]

However, this does not work when the method is decorated.

class Rator:

    def __init__(self):
        pass

    def __call__(self, func):
        def wrapper(instance, **kwargs):
            func(instance, **kwargs)
        return wrapper


class Klass:

    def method(self, var_one=None, var_two=None):
        pass

    @Rator()
    def decorated_method(self, var_one=None, var_two=None):
        pass


if __name__ == '__main__':
    klass = Klass()
    print("method args is " + str(klass.method.__code__.co_varnames))
    print("decorated method args is " + str(klass.decorated_method.__code__.co_varnames))

Outputs

method args is ('self', 'var_one', 'var_two')
decorated method args is ('instance',)

A solution that does not require change of the decorator is preferred.

I know that this question is a duplicate of How to retrieve method arguments of a decorated python method, but it has since long been dead.

Hampus
  • 2,769
  • 1
  • 22
  • 38
  • 2
    Have you tried [`functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps)? See [Preserving signatures of decorated functions](https://stackoverflow.com/q/147816). Also, consider using [`inspect`](https://docs.python.org/3/library/inspect.html) to retrieve function parameters (see [Getting method parameter names in Python](https://stackoverflow.com/q/218616)). – jdehesa Jan 28 '19 at 10:30
  • @jdehesa Yep. That solved the problem. But a solution that doesn't require changes to the decorator would have been better. For the cases where the decorator is part of a third party library. – Hampus Jan 28 '19 at 13:17

1 Answers1

4

You need wrap you decorator with functools.wraps, then you can can inspect the methods via inspect.signature():

import inspect
from functools import wraps


class Rator:
    def __init__(self):
        pass

    def __call__(self, func, **kwargs):
        @wraps(func)
        def wrapper(instance):
            func(instance, **kwargs)
        return wrapper


class Klass:
    def method(self, var_one=None, var_two=None):
        pass

    @Rator()
    def decorated_method(self, argument1, var_one=None, var_two=None):
        pass


if __name__ == '__main__':
    klass = Klass()
    print("method args is ", inspect.signature(klass.method))
    print("decorated method args is ", inspect.signature(klass.decorated_method))

Prints:

method args is  (var_one=None, var_two=None)
decorated method args is  (argument1, var_one=None, var_two=None)
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • Thanks. Too bad it is the decorator that needs to change. So this would not solve the cases where the decorator code is not under my control. I guess wrapping the decorator would be the solution to that but not ideal. – Hampus Jan 28 '19 at 13:16