¿Cómo puedo leer la firma de una función, incluidos los valores de argumento predeterminados?

129

Dado un objeto de función, ¿cómo puedo obtener su firma? Por ejemplo, para:

def myMethod(firt, second, third='something'):
    pass

Me gustaría llegar "myMethod(firt, second, third='something')".

Spì
fuente
3
¿Puede explicar su pregunta específica y dar un ejemplo con el resultado esperado?
jhwist
Presumiblemente, está buscando funcionalidad en Python o en bibliotecas de terceros que devolverán la firma de un método (nombres y tipos de parámetros y valor de retorno) dado el nombre del método.
Michael Petrotta
1
Firma como en cómo llamarlo y tal? Prueba, help(yourmethod)por ejemplohelp(map)
Nick T
Solo parámetros: stackoverflow.com/questions/218616/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

187
import inspect

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

print(inspect.getargspec(foo))
# ArgSpec(args=['a', 'b', 'x'], varargs=None, keywords=None, defaults=('blah',))

Sin embargo, tenga en cuenta que inspect.getargspec()está en desuso desde Python 3.0.

Python 3.0--3.4 recomienda inspect.getfullargspec().

Python 3.5+ recomienda inspect.signature().

unutbu
fuente
AttributeError: el objeto 'módulo' no tiene atributo 'getargspec'
Spì
3
@Spi, estás llamando inspect.getargspeca un módulo, no a una función.
Mike Graham el
Gracias, el problema fue con Eclipse que no vio el módulo de inspección
Spì
Si una función tiene anotaciones de argumentos o argumentos de solo palabras clave (= si está utilizando Python 3), debe llamar en su getfullargspeclugar. ( ValueError: Function has keyword-only arguments or annotations, use getfullargspec() API which can support them)
badp
2
@darth_coder: en Python2, getargspecaumenta TypeErrorsi la entrada no se reconoce como una función de Python , es decir, una función implementada en Python. En CPython, Exception.__init__se implementa en C, de ahí el TypeError. Deberá verificar el código fuente para comprender la firma de la llamada. En Python3, getargspecse implementa de manera diferente, y allí inspect.getargspec(Exception.__init__)devuelve una ArgSpecinstancia.
unutbu
44

Podría decirse que la forma más fácil de encontrar la firma de una función sería 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)

Además, en Python 3 se agregó un método al inspectmódulo llamado signature, que está diseñado para representar la firma de un objeto invocable y su anotación de retorno :

>>> 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'>
Stefan van den Akker
fuente
3
inspect.signaturetambién está disponible para Python 2 a través del funcsigsproyecto de backport: pypi.python.org/pypi/funcsigs
ncoghlan
14
#! /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
fuente
¿Alguna explicación de lo que se supone que debe hacer?
grantmcconnaughey
1
Se agregaron comentarios al código de muestra, espero que ayude.
Arup Malakar
Cosas preciosas Sería aún mejor si pudieras ajustarlo para que funcione con def foo(a, *, b:int, **kwargs)llamado confoo(4, b=3.3)
ofer.sheffer
8

Intenta llamar helpa un objeto para averiguarlo.

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

append(...)
    L.append(object) -- append object to end
Mike Graham
fuente
7

Tal vez un poco tarde para la fiesta, pero si también desea mantener el orden de los argumentos y sus valores predeterminados , puede usar el módulo Árbol de sintaxis abstracta (ast) .

Aquí hay una prueba de concepto (tenga cuidado con el código para ordenar los argumentos y relacionarlos con sus valores predeterminados definitivamente se puede mejorar / aclarar más):

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
fuente
Tenga en cuenta que los argumentos no predeterminados no pueden seguir los argumentos predeterminados , por lo que podemos simplemente hacerlos coincidir desde la cola.
Evgeni Sergeev
5

Si todo lo que intenta hacer es imprimir la función, 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)

Si está intentando analizar realmente la firma de la función, utilice argspec del módulo de inspección. Tenía que hacer eso al validar la función de script de enlace de un usuario en un marco general.

Al Conrad
fuente
3

Código de ejemplo:

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
fuente
2

Use% pdef en la línea de comando (IPython), imprimirá solo la firma.

p.ej %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
fuente