Pregunta

Dada la función de Python:

def aMethod(arg1, arg2):
    pass

¿Cómo puedo extraer el número y los nombres de los argumentos?Es decir, dado que tengo una referencia a func, quiero que func.[algo] 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 clave.Es decir, ¿cómo se vería el decorador que imprimiera "a,b" cuando llamo aMethod("a", "b")?

¿Fue útil?

Solución

Eche un vistazo al inspeccionar - esto hará la inspección de los distintos propiedades de objeto de código para usted.

>>> inspect.getfullargspec(aMethod)
(['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á ValueError en el caso de que use inspect.getfullargspec() con una función incorporada.

Desde Python 3.3, puede usar también el inspect.signature () para conocer la firma de llamada de un objeto invocable:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

Otros consejos

En CPython, el número de argumentos es

aMethod.func_code.co_argcount

y sus nombres están al principio de

aMethod.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 " pass-through " argumentos es definir su función con la firma func(*args, **kwargs). Esto se usa mucho en p. Ej. matplotlib, donde la capa API externa pasa muchos argumentos de palabras clave a la API de nivel inferior.

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 los **kwargs son 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]

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.

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}

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.

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())

Para 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

En Python 3. + con el objeto Signature a la mano, una manera fácil de obtener una asignación entre los nombres de los argumentos y los valores es usar el método bind() 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}

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

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

Actualización para 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={})

En Python 3, a continuación se hace *args y **kwargs en un dict (usar OrderedDict para Python <3.6 para mantener dict pedidos):

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

Para actualizar un poco Respuesta de Brian , ahora hay un buen backport de inspect.signature que puede usar en 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á interesado en jugar con Signature objetos e incluso crear funciones con firmas aleatorias dinámicamente, puede echar un vistazo a mi makefun proyecto.

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

Parece hacer exactamente lo que se pide de forma súper sencilla...

Debe llamarse desde dentro del alcance de la función.

Pero ten cuidado que volverá. todo variables locales, así que asegúrese de hacerlo al principio de la función si es necesario.

También tenga en cuenta que, como se señala en los comentarios, esto no permite hacerlo 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top