¿Puede enumerar los argumentos de palabras clave que recibe una función?

104

Tengo un dictado, que necesito para pasar clave / valores como argumentos de palabra clave .. Por ejemplo ..

d_args = {'kw1': 'value1', 'kw2': 'value2'}
example(**d_args)

Esto funciona bien, pero si hay valores en el d_args dict que no son aceptados por la examplefunción, obviamente muere. Digamos, si la función de ejemplo se define comodef example(kw2):

Esto es un problema ya que no controlo ni la generación de d_args, ni la examplefunción .. Ambos provienen de módulos externos, y examplesolo aceptan algunos de los argumentos de palabras clave del dict ..

Idealmente solo haría

parsed_kwargs = feedparser.parse(the_url)
valid_kwargs = get_valid_kwargs(parsed_kwargs, valid_for = PyRSS2Gen.RSS2)
PyRSS2Gen.RSS2(**valid_kwargs)

Probablemente solo filtraré el dict, de una lista de argumentos de palabras clave válidos, pero me preguntaba: ¿Hay alguna manera de enumerar programáticamente los argumentos de palabras clave que toma una función específica?

dbr
fuente

Respuestas:

150

Un poco más agradable que inspeccionar el objeto de código directamente y calcular las variables es usar el módulo de inspección.

>>> import inspect
>>> def func(a,b,c=42, *args, **kwargs): pass
>>> inspect.getargspec(func)
(['a', 'b', 'c'], 'args', 'kwargs', (42,))

Si desea saber si se puede llamar con un conjunto particular de argumentos, necesita los argumentos sin un valor predeterminado ya especificado. Estos se pueden obtener mediante:

def getRequiredArgs(func):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if defaults:
        args = args[:-len(defaults)]
    return args   # *args and **kwargs are not required, so ignore them.

Entonces, una función para decir lo que te falta de tu dictado particular es:

def missingArgs(func, argdict):
    return set(getRequiredArgs(func)).difference(argdict)

De manera similar, para verificar si hay argumentos no válidos, use:

def invalidArgs(func, argdict):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if varkw: return set()  # All accepted
    return set(argdict) - set(args)

Entonces, una prueba completa si se puede llamar es:

def isCallableWithArgs(func, argdict):
    return not missingArgs(func, argdict) and not invalidArgs(func, argdict)

(Esto es bueno solo en lo que respecta al análisis de arg de Python. Obviamente, cualquier comprobación de tiempo de ejecución para valores no válidos en kwargs no se puede detectar).

Brian
fuente
¡Agradable! ¡No conocía esta función!
DzinX
1
Dado que el método que utiliza objetos de código es más o menos idéntico, ¿hay alguna ventaja en haber tenido que importar un módulo más ...?
jmetz
@jmets - definitivamente - virtualmente siempre es mejor usar un módulo de biblioteca que rodar uno propio. Además, los atributos del objeto de código son más internos y están sujetos a cambios (por ejemplo, tenga en cuenta que esto se movió al código en pyhon3). Usar el módulo como interfaz lo prepara un poco más en caso de que alguna de estas partes internas cambie. También hará cosas que quizás no haya pensado hacer, como lanzar un error de tipo apropiado en funciones que no puede inspeccionar (por ejemplo, funciones C).
Brian
13
inspect.getargspec(f)está en desuso desde Python 3.0; el método moderno es inspect.signature(f).
gerrit
Para su información, si desea admitir Cython y Python, este método no funciona en una función Cython'd. La co_varnamesopción, por otro lado, funciona en ambos.
algo
32

Esto imprimirá los nombres de todos los argumentos aceptables, palabras clave y no palabras clave:

def func(one, two="value"):
    y = one, two
    return y
print func.func_code.co_varnames[:func.func_code.co_argcount]

Esto se debe a que primero co_varnamesson siempre los parámetros (después son las variables locales, como yen el ejemplo anterior).

Entonces ahora podrías tener una función:

def getValidArgs(func, argsDict):
    '''Return dictionary without invalid function arguments.'''
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validArgs)

Que luego podrías usar así:

>>> func(**getValidArgs(func, args))

EDITAR : Una pequeña adición: si realmente necesita solo argumentos de palabra clave de una función, puede usar el func_defaultsatributo para extraerlos:

def getValidKwargs(func, argsDict):
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    kwargsLen = len(func.func_defaults) # number of keyword arguments
    validKwargs = validArgs[-kwargsLen:] # because kwargs are last
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validKwargs)

Ahora puede llamar a su función con argumentos conocidos, pero kwargs extraídos, por ejemplo:

func(param1, param2, **getValidKwargs(func, kwargsDict))

Esto supone que funcno utiliza magia *argso no **kwargsen su firma.

DzinX
fuente
¿Qué pasa si solo quiero imprimir argumentos de "palabra clave" "claves"?
Jia
7

En Python 3.0:

>>> import inspect
>>> import fileinput
>>> print(inspect.getfullargspec(fileinput.input))
FullArgSpec(args=['files', 'inplace', 'backup', 'bufsize', 'mode', 'openhook'],
varargs=None, varkw=None, defaults=(None, 0, '', 0, 'r', None), kwonlyargs=[], 
kwdefaults=None, annotations={})
jfs
fuente
7

Para una solución de Python 3, puede usar inspect.signaturey filtrar de acuerdo con el tipo de parámetros que le gustaría conocer.

Tomando una función de muestra con parámetros posicionales o de palabras clave, solo palabras clave, var posicional y var:

def spam(a, b=1, *args, c=2, **kwargs):
    print(a, b, args, c, kwargs)

Puede crear un objeto de firma para él:

from inspect import signature
sig =  signature(spam)

y luego filtrar con una lista de comprensión para descubrir los detalles que necesita:

>>> # positional or keyword
>>> [p.name for p in sig.parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD]
['a', 'b']
>>> # keyword only
>>> [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]
['c']

y, de manera similar, para posicionales var usando p.VAR_POSITIONALy la palabra clave var with VAR_KEYWORD.

Además, puede agregar una cláusula al if para verificar si existe un valor predeterminado al verificar si p.defaultes igual p.empty.

Dimitris Fasarakis Hilliard
fuente
3

Ampliando la respuesta de DzinX:

argnames = example.func_code.co_varnames[:func.func_code.co_argcount]
args = dict((key, val) for key,val in d_args.iteritems() if key in argnames)
example(**args)
Claudiu
fuente