161

Given a function object, how can I get its signature? For example, for:

def my_method(first, second, third='something'):
    pass

I would like to get "my_method(first, second, third='something')".

Spì
  • 1,875
  • 2
  • 14
  • 10

8 Answers8

229
import inspect

def foo(a, b, x='blah'):
    pass

print(inspect.signature(foo))
# (a, b, x='blah')

Python 3.5+ recommends inspect.signature().

wjandrea
  • 23,210
  • 7
  • 49
  • 68
unutbu
  • 777,569
  • 165
  • 1,697
  • 1,613
  • AttributeError: 'module' object has no attribute 'getargspec' – Spì Apr 20 '10 at 17:34
  • 4
    @Spi, you are calling `inspect.getargspec` on a module, not a function. – Mike Graham Apr 20 '10 at 17:36
  • Thanks, the problem was with Eclipse that did not see the inspect module – Spì Apr 21 '10 at 08:45
  • 1
    If a function has argument annotations or keyword only arguments (= if you are using Python 3) you have to call `getfullargspec` instead. (`ValueError: Function has keyword-only arguments or annotations, use getfullargspec() API which can support them`) – badp Jul 13 '14 at 09:33
  • @unutbu , I tried the same on `Exception.__init__` but got an error `TypeError: is not a Python function`. Any help on how can we get the signature of functions of types. – Krishna Oza Aug 03 '18 at 04:49
  • 2
    @darth_coder: In Python2, `getargspec` raises `TypeError` if the input is not recognized as a *Python* function -- that is, a function implemented in Python. In CPython, `Exception.__init__` is implemented in C, hence the `TypeError`. You'll have to check the source code to understand the call signature. In Python3, `getargspec` is implemented differently, and there `inspect.getargspec(Exception.__init__)` returns a `ArgSpec` instance. – unutbu Aug 04 '18 at 01:10
  • is there a way to extract the list of possible values for each argument as well? – Kiann Oct 01 '19 at 22:21
  • I am not getting the proper info... `` – jangorecki Oct 14 '19 at 15:11
55

Arguably the easiest way to find the signature for a function would be help(function):

>>> def function(arg1, arg2="foo", *args, **kwargs): pass
>>> help(function)
Help on function function in module __main__:

function(arg1, arg2='foo', *args, **kwargs)

Also, in Python 3 a method was added to the inspect module called signature, which is designed to represent the signature of a callable object and its return annotation:

>>> from inspect import signature
>>> def foo(a, *, b:int, **kwargs):
...     pass

>>> sig = signature(foo)

>>> str(sig)
'(a, *, b:int, **kwargs)'

>>> str(sig.parameters['b'])
'b:int'

>>> sig.parameters['b'].annotation
<class 'int'>
Flimm
  • 115,689
  • 38
  • 227
  • 240
Stefan van den Akker
  • 6,242
  • 7
  • 43
  • 62
  • 5
    `inspect.signature` is also available for Python 2 via the `funcsigs` backport project: https://pypi.python.org/pypi/funcsigs – ncoghlan Jan 17 '16 at 03:07
  • 1
    What's the difference between `inspect.signature` and `typing.get_type_hints`? – Flimm Oct 19 '20 at 15:49
15
#! /usr/bin/env python

import inspect
from collections import namedtuple

DefaultArgSpec = namedtuple('DefaultArgSpec', 'has_default default_value')

def _get_default_arg(args, defaults, arg_index):
    """ Method that determines if an argument has default value or not,
    and if yes what is the default value for the argument

    :param args: array of arguments, eg: ['first_arg', 'second_arg', 'third_arg']
    :param defaults: array of default values, eg: (42, 'something')
    :param arg_index: index of the argument in the argument array for which,
    this function checks if a default value exists or not. And if default value
    exists it would return the default value. Example argument: 1
    :return: Tuple of whether there is a default or not, and if yes the default
    value, eg: for index 2 i.e. for "second_arg" this function returns (True, 42)
    """
    if not defaults:
        return DefaultArgSpec(False, None)

    args_with_no_defaults = len(args) - len(defaults)

    if arg_index < args_with_no_defaults:
        return DefaultArgSpec(False, None)
    else:
        value = defaults[arg_index - args_with_no_defaults]
        if (type(value) is str):
            value = '"%s"' % value
        return DefaultArgSpec(True, value)

def get_method_sig(method):
    """ Given a function, it returns a string that pretty much looks how the
    function signature would be written in python.

    :param method: a python method
    :return: A string similar describing the pythong method signature.
    eg: "my_method(first_argArg, second_arg=42, third_arg='something')"
    """

    # The return value of ArgSpec is a bit weird, as the list of arguments and
    # list of defaults are returned in separate array.
    # eg: ArgSpec(args=['first_arg', 'second_arg', 'third_arg'],
    # varargs=None, keywords=None, defaults=(42, 'something'))
    argspec = inspect.getargspec(method)
    arg_index=0
    args = []

    # Use the args and defaults array returned by argspec and find out
    # which arguments has default
    for arg in argspec.args:
        default_arg = _get_default_arg(argspec.args, argspec.defaults, arg_index)
        if default_arg.has_default:
            args.append("%s=%s" % (arg, default_arg.default_value))
        else:
            args.append(arg)
        arg_index += 1
    return "%s(%s)" % (method.__name__, ", ".join(args))


if __name__ == '__main__':
    def my_method(first_arg, second_arg=42, third_arg='something'):
        pass

    print get_method_sig(my_method)
    # my_method(first_argArg, second_arg=42, third_arg="something")
Arup Malakar
  • 513
  • 6
  • 9
10

Try calling help on an object to find out about it.

>>> foo = [1, 2, 3]
>>> help(foo.append)
Help on built-in function append:

append(...)
    L.append(object) -- append object to end
Mike Graham
  • 69,495
  • 14
  • 96
  • 129
8

Maybe a bit late to the party, but if you also want to keep the order of the arguments and their defaults, then you can use the Abstract Syntax Tree module (ast).

Here's a proof of concept (beware the code to sort the arguments and match them to their defaults can definitely be improved/made more clear):

import ast

for class_ in [c for c in module.body if isinstance(c, ast.ClassDef)]:
    for method in [m for m in class_.body if isinstance(m, ast.FunctionDef)]:
        args = []
        if method.args.args:
            [args.append([a.col_offset, a.id]) for a in method.args.args]
        if method.args.defaults:
            [args.append([a.col_offset, '=' + a.id]) for a in method.args.defaults]
        sorted_args = sorted(args)
        for i, p in enumerate(sorted_args):
            if p[1].startswith('='):
                sorted_args[i-1][1] += p[1]
        sorted_args = [k[1] for k in sorted_args if not k[1].startswith('=')]

        if method.args.vararg:
            sorted_args.append('*' + method.args.vararg)
        if method.args.kwarg:
            sorted_args.append('**' + method.args.kwarg)

        signature = '(' + ', '.join(sorted_args) + ')'

        print method.name + signature
Jir
  • 2,633
  • 5
  • 39
  • 63
  • Note that [non-default arguments cannot follow default arguments](https://stackoverflow.com/q/16932825/1143274), so we can simply match them up from the tail? – Evgeni Sergeev Mar 29 '18 at 18:33
6

If all you're trying to do is print the function then use pydoc.

import pydoc    

def foo(arg1, arg2, *args, **kwargs):                                                                    
    '''Some foo fn'''                                                                                    
    pass                                                                                                 

>>> print pydoc.render_doc(foo).splitlines()[2]
foo(arg1, arg2, *args, **kwargs)

If you're trying to actually analyze the function signature then use argspec of the inspection module. I had to do that when validating a user's hook script function into a general framework.

Al Conrad
  • 1,320
  • 15
  • 11
6

Use %pdef in the command line (IPython), it will print only the signature.

e.g. %pdef np.loadtxt

 np.loadtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0, encoding='bytes')
liyuan
  • 525
  • 3
  • 15
6

Example code:

import inspect
from collections import OrderedDict


def get_signature(fn):
    params = inspect.signature(fn).parameters
    args = []
    kwargs = OrderedDict()
    for p in params.values():
        if p.default is p.empty:
            args.append(p.name)
        else:
            kwargs[p.name] = p.default
    return args, kwargs


def test_sig():
    def fn(a, b, c, d=3, e="abc"):
        pass

    assert get_signature(fn) == (
        ["a", "b", "c"], OrderedDict([("d", 3), ("e", "abc")])
    )
weaming
  • 4,467
  • 1
  • 17
  • 13