¿Cómo obtener nombres de parámetros de método?

236

Dada la función Python:

def a_method(arg1, arg2):
    pass

¿Cómo puedo extraer el número y los nombres de los argumentos? Es decir, dado que tengo una referencia func, quiero func.[something]que regrese ("arg1", "arg2").

El escenario de uso para esto es que tengo un decorador, y deseo usar los argumentos del método en el mismo orden en que aparecen para la función real como una clave. Es decir, ¿cómo se vería el decorador que impreso "a,b"cuando llamo a_method("a", "b")?

Staale
fuente
1
Para obtener una lista diferente de respuestas a una pregunta casi idéntica, vea esta otra publicación de stackoverflow
dan mackinlay
1
Su título es engañoso: cuando uno dice 'método' wrt la palabra 'función', generalmente se piensa en un método de clase. Para la función, su respuesta seleccionada (de Jouni K. Seppanen) es buena. Pero para el método (clase), no funciona y se debe usar la solución de inspección (de Brian).
Juh_

Respuestas:

352

Eche un vistazo al inspectmódulo: esto hará la inspección de las diferentes propiedades del objeto de código por usted.

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

Los otros resultados son el nombre de las variables * args y ** kwargs, y los valores predeterminados proporcionados. es decir.

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

Tenga en cuenta que algunas llamadas no pueden ser introspectables en ciertas implementaciones de Python. Por ejemplo, en CPython, algunas funciones integradas definidas en C no proporcionan metadatos sobre sus argumentos. Como resultado, obtendrá un ValueErrorsi lo usa inspect.getfullargspec()en una función incorporada.

Desde Python 3.3, puede usar inspect.signature()para ver la firma de la llamada de un objeto invocable:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Brian
fuente
29
¿Cómo podría saber el código que el parámetro predeterminado (4,)corresponde cespecíficamente al parámetro de la palabra clave ?
fatuhoku
63
@fatuhoku Me preguntaba lo mismo. Resulta que no es ambiguo ya que solo puede agregar argumentos predeterminados al final en un bloque contiguo. De los documentos: "si esta tupla tiene n elementos, corresponden a los últimos n elementos enumerados en los
argumentos
99
Creo que desde Python 3.x getargspec (...) se reemplaza por inspector.signature (func)
Diego Andrés Díaz Espinoza
2
Modificado en la versión 2.6: Devuelve una tupla ArgSpec con nombre (args, varargs, palabras clave, valores predeterminados).
locutor
44
Así es, @ DiegoAndrésDíazEspinoza: en Python 3, inspect.getargspecestá en desuso , pero el reemplazo lo está inspect.getfullargspec.
j08lue
100

En CPython, el número de argumentos es

a_method.func_code.co_argcount

y sus nombres están al comienzo de

a_method.func_code.co_varnames

Estos son detalles de implementación de CPython, por lo que probablemente esto no funcione en otras implementaciones de Python, como IronPython y Jython.

Una forma portátil de admitir argumentos de "transferencia" es definir su función con la firma func(*args, **kwargs). Esto se usa mucho, por ejemplo , en matplotlib , donde la capa API externa pasa muchos argumentos de palabras clave a la API de nivel inferior.

Jouni K. Seppänen
fuente
co_varnames funciona con Python estándar, pero este método no es preferible ya que también mostrará los argumentos internos.
MattK
11
¿Por qué no usar aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl
No trabajar con argumentos después *args, por ejemplo:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin
@Nikolay ver stackoverflow.com/questions/147816/…
Brian McCutchon
Utilice inspeccionar en su lugar. De lo contrario, su código no funciona bien con functools.wraps en 3.4+. Ver stackoverflow.com/questions/147816/…
Brian McCutchon
22

En un método de decorador, puede enumerar los argumentos del método original de esta manera:

import inspect, itertools 

def my_decorator():

        def decorator(f):

            def wrapper(*args, **kwargs):

                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

Si **kwargsson importantes para usted, entonces será un poco complicado:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

Ejemplo:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]
Mehdi Behrooz
fuente
Esta respuesta es parcialmente obsoleta y debe actualizarse.
Imago
15

Creo que lo que estás buscando es el método local:


In [6]: def test(a, b):print locals()
   ...: 

In [7]: test(1,2)              
{'a': 1, 'b': 2}
Damian
fuente
77
Esto es inútil fuera de una función que es el contexto de interés aquí (decorador).
Piotr Dobrogost
8
En realidad, exactamente lo que estaba buscando, aunque no es la respuesta a la pregunta aquí.
javabeangrinder
15

La versión de Python 3 es:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

El método devuelve un diccionario que contiene args y kwargs.

Argaen
fuente
Tenga en cuenta que [:fn.__code__.co_argcount]es muy importante si está buscando los argumentos de la función; de lo contrario, también incluye los nombres creados dentro de la función.
Soren Bjornstad
13

Aquí hay algo que creo que funcionará para lo que quieres, usando un decorador.

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

Ejecútelo, producirá el siguiente resultado:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.
hlzr
fuente
11

Python 3.5+:

DeprecationWarning: inspect.getargspec () está en desuso, use inspect.signature () en su lugar

Entonces anteriormente:

func_args = inspect.getargspec(function).args

Ahora:

func_args = list(inspect.signature(function).parameters.keys())

Probar:

'arg' in list(inspect.signature(function).parameters.keys())

Dado que tenemos la función 'función' que toma el argumento 'arg', esto se evaluará como Verdadero, de lo contrario, como Falso.

Ejemplo de la consola de Python:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True
Peter Majko
fuente
8

¡En Python 3. + con el Signatureobjeto en mano, una manera fácil de obtener una asignación entre los nombres de los argumentos y los valores es usar el bind()método de la Firma !

Por ejemplo, aquí hay un decorador para imprimir un mapa como ese:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}
Kfir Eisner
fuente
6

Aquí hay otra forma de obtener los parámetros de la función sin usar ningún módulo.

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {}
    for i in sorted(kwargs.keys(), key=sorter.get):
        sorted_kwargs[i] = kwargs[i]      
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a


print(get_parameters(f))

Salida:

(('a', 'b'), {'c': 'hello', 'd': 'world'})
dildeolupbiten
fuente
2

Devuelve una lista de nombres de argumentos, se ocupa de parciales y funciones regulares:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)
lovesh
fuente
2

Actualización para la respuesta de Brian :

Si una función en Python 3 tiene argumentos de solo palabras clave, entonces debe usar inspect.getfullargspec:

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

produce esto:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})
ASMik09
fuente
2

En python 3, a continuación es para hacer *argsy **kwargsen un dict(uso OrderedDictpara python <3.6 para mantener los dictpedidos):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper
Alfa
fuente
1

inspect.signaturees muy lento. La forma más rápida es

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')
Nikolay Makhalin
fuente
0

Para actualizar un poco la respuesta de Brian , ahora hay un buen backport de inspect.signatureque se puede utilizar en las versiones anteriores de Python: funcsigs. Entonces mi preferencia personal iría por

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Por diversión, si estás interesado en jugar con Signatureobjetos e incluso crear funciones con firmas aleatorias dinámicamente, puedes echar un vistazo a mi makefunproyecto.

smarie
fuente
-3

¿Qué pasa dir()y vars()ahora?

Parece hacer exactamente lo que se le pide súper simplemente ...

Debe llamarse desde el ámbito de la función.

Pero tenga cuidado de que devolverá todas las variables locales, así que asegúrese de hacerlo al comienzo de la función si es necesario.

También tenga en cuenta que, como se señaló en los comentarios, esto no permite que se haga desde fuera del alcance. Entonces, no es exactamente el escenario de OP, pero aún coincide con el título de la pregunta. De ahí mi respuesta.

jeromej
fuente
dir () devuelve la lista de todos los nombres de variables ['var1', 'var2'], vars () devuelve el diccionario en forma {'var1': 0, 'var2': 'algo'} desde el ámbito local actual. Si alguien quiere usar nombres de variables de argumento más adelante en la función, debe guardar en otra variable local, porque llamarlo más tarde en la función donde podría declarar otras variables locales "contaminará" esta lista. En el caso de que quieran usarlo fuera de la función, deben ejecutar la función al menos una vez y guardarla en una variable global. Por lo tanto, es mejor usar el módulo de inspección.
Peter Majko