How can I read a function's signature including default argument values?
Asked Answered
S

9

190

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')".

Serried answered 20/4, 2010 at 17:17 Comment(4)
Can you please elaborate on your specific question and maybe give an example with the expected result?Byram
Presumably he's looking for functionality in Python or third-party libraries that will return a method's signature (names and types of parameters and return value) given the method's name.Kibitka
Signature as in how to call it and such? Try help(yourmethod) e.g. help(map)Brunn
Just parameters: #219116Loyceloyd
R
263
import inspect

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

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

Python 3.5+ recommends inspect.signature().

Riannon answered 20/4, 2010 at 17:29 Comment(7)
@Spi, you are calling inspect.getargspec on a module, not a function.Regatta
Thanks, the problem was with Eclipse that did not see the inspect moduleEstreat
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)Trimetallic
@Riannon , I tried the same on Exception.__init__ but got an error TypeError: <slot wrapper '__init__' of 'exceptions.Exception' objects> is not a Python function. Any help on how can we get the signature of functions of types.Ber
@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.Riannon
is there a way to extract the list of possible values for each argument as well?Cush
I am not getting the proper info... <function fread at 0x7f660eb6d158>Helminthic
O
61

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'>
Orbadiah answered 1/8, 2014 at 9:17 Comment(3)
inspect.signature is also available for Python 2 via the funcsigs backport project: pypi.python.org/pypi/funcsigsPorter
What's the difference between inspect.signature and typing.get_type_hints?Sutphin
Use inspect.signature(func).parameters.keys() if you want a list of the parameters.Ortegal
S
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")
Situla answered 26/6, 2012 at 7:15 Comment(3)
Any explanation at all as to what this is supposed to do?Angelitaangell
Added comments to the code sample, hope that helps.Situla
Lovely stuff. Would be even better if you could adjust it to work with def foo(a, *, b:int, **kwargs) called with foo(4, b=3.3)Konyn
R
11

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
Regatta answered 20/4, 2010 at 17:19 Comment(0)
B
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
Binal answered 2/7, 2015 at 17:5 Comment(1)
Note that non-default arguments cannot follow default arguments, so we can simply match them up from the tail?Maker
D
7

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.

Disingenuous answered 11/8, 2016 at 14:28 Comment(0)
M
7

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')
Mungovan answered 13/7, 2018 at 12:12 Comment(0)
C
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")])
    )
Constitution answered 24/8, 2018 at 11:1 Comment(0)
S
0

Another late entry. My point isn't to, again, print the sig, or display help. It is to programmatically introspect function parameters (when I got to this question I was looking to check Django view functions by looking at functions with request as a first parameter name).

The key is the Signature.parameters attribute, which is actually not that complicated (note that inspect._empty is akin to None in concept).

import inspect
from typing import Any, cast

def check_signature(func : "Callable") -> None:
    funcname = func.__name__
    try:
        # class inspect.Signature(parameters=None, *, return_annotation=Signature.empty)
        # see https://docs.python.org/3/library/inspect.html#inspect.Signature
        sig = inspect.signature(func)
    except (ValueError,) as e: 
        print(f"\n\n`{funcname}` has no signature")
        return
    
    print(f"\n\n`{funcname}{sig}` parameters:")
    for position, (name,param) in enumerate(sig.parameters.items()):
        # class inspect.Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
        # see https://docs.python.org/3/library/inspect.html#inspect.Parameter
        print(f"  {position} {name:30.30}  kind={param.kind.description.replace(' ','_')} / default={param.default if param.default is not inspect._empty else ''} / annotation={param.annotation if param.annotation is not inspect._empty else ''}")

class Foo:
    def bar(self, zoom : int =2):
        pass

for func in [Any, check_signature, print, Foo.bar, Foo().bar, isinstance, issubclass, cast]:
    check_signature(func)

output:



`Any(*args, **kwds)` parameters:
  0 args                            kind=variadic_positional / default= / annotation=
  1 kwds                            kind=variadic_keyword / default= / annotation=


`check_signature(func: 'Callable') -> None` parameters:
  0 func                            kind=positional_or_keyword / default= / annotation=Callable


`print` has no signature


`bar(self, zoom: int = 2)` parameters:
  0 self                            kind=positional_or_keyword / default= / annotation=
  1 zoom                            kind=positional_or_keyword / default=2 / annotation=<class 'int'>


`bar(zoom: int = 2)` parameters:
  0 zoom                            kind=positional_or_keyword / default=2 / annotation=<class 'int'>


`isinstance(obj, class_or_tuple, /)` parameters:
  0 obj                             kind=positional-only / default= / annotation=
  1 class_or_tuple                  kind=positional-only / default= / annotation=


`issubclass(cls, class_or_tuple, /)` parameters:
  0 cls                             kind=positional-only / default= / annotation=
  1 class_or_tuple                  kind=positional-only / default= / annotation=


`cast(typ, val)` parameters:
  0 typ                             kind=positional_or_keyword / default= / annotation=
  1 val                             kind=positional_or_keyword / default= / annotation=

In the OP's case you'd just check for param.default is not inspect._empty. I'd opt for returning a dict[str, Any] of those.

To make things a bit easier on myself, I went and added a pydantic wrapper to the whole thing:

class FuncSignature(BaseModel):

    class Config:
        arbitrary_types_allowed = True

    funcname: str
    sig : inspect.Signature
    by_pos : dict[int,inspect.Parameter]
    by_name: dict[str,inspect.Parameter]

    undefined = inspect._empty

def get_signature(func : "Callable") -> FuncSignature:
    """return signature for a function"""
    funcname = func.__name__
    sig = inspect.signature(func)
    by_name, by_pos = {},{}

    for position, (name,param) in enumerate(sig.parameters.items()):
        by_name[name] = param
        by_pos[position] = param

    return FuncSignature(funcname=funcname, sig=sig,by_name=by_name,by_pos=by_pos)

which allowed me to

def myview(request, rdbname):
    pass

res = check_signature(myview)
print(res.by_pos[0].name == "request")  # True
Supercharge answered 18/11, 2022 at 2:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.