Você pode listar os argumentos de palavra-chave que uma função recebe?

104

Eu tenho um dicionário que preciso passar chave / valores como argumentos de palavra-chave .. Por exemplo ..

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

Isso funciona bem, mas se houver valores no d_args dict que não são aceitos pela examplefunção, ele obviamente morre. Digamos, se a função de exemplo for definida comodef example(kw2):

Isso é um problema, pois eu não controlo a geração do d_args, ou da examplefunção .. Ambos vêm de módulos externos e examplesó aceitam alguns dos argumentos-chave do dicionário.

Idealmente, eu apenas faria

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

Provavelmente irei filtrar apenas o dict, de uma lista de argumentos de palavra-chave válidos, mas eu estava me perguntando: Existe uma maneira de listar programaticamente os argumentos de palavra-chave que uma função específica usa?

dbr
fonte

Respostas:

150

Um pouco mais agradável do que inspecionar o objeto de código diretamente e calcular as variáveis ​​é usar o módulo de inspeção.

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

Se você quiser saber se pode ser chamado com um determinado conjunto de args, você precisa dos args sem um padrão já especificado. Isso pode ser obtido por:

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.

Então, uma função para dizer o que está faltando em seu dicionário particular é:

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

Da mesma forma, para verificar se há argumentos inválidos, use:

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

E assim, um teste completo para saber se ele pode ser chamado é:

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

(Isso é bom apenas no que diz respeito à análise de argumentos do python. Qualquer verificação de tempo de execução para valores inválidos em kwargs obviamente não pode ser detectada.)

Brian
fonte
Agradável! Eu não conhecia essa função!
DzinX
1
Dado que o método que usa objetos de código é mais ou menos idêntico, há uma vantagem em ter que importar mais um módulo ...?
jmetz
@jmets - definitivamente - é virtualmente sempre melhor usar um módulo de biblioteca do que criar o seu próprio. Além disso, os atributos no objeto de código são mais internos e podem ser alterados (por exemplo, observe que isso mudou para o código em pyhon3). Usar o módulo como interface garante um pouco mais de futuro, caso algum desses componentes internos mude. Ele também fará coisas que você pode não ter pensado em fazer, como lançar um erro de tipo apropriado em funções que você não pode inspecionar (por exemplo, funções C).
Brian de
13
inspect.getargspec(f)está obsoleto desde o Python 3.0; o método moderno é inspect.signature(f).
gerrit
Para sua informação, se você deseja oferecer suporte a Cython e Python, este método não funciona em uma função Cython. A co_varnamesopção, por outro lado, funciona em ambos.
jornada
32

Isso imprimirá os nomes de todos os argumentos passáveis, palavras-chave e não palavras-chave:

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

Isso ocorre porque primeiro co_varnamessão sempre os parâmetros (a seguir são as variáveis ​​locais, como yno exemplo acima).

Então agora você pode ter uma função:

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 você pode usar assim:

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

EDIT : Uma pequena adição: se você realmente precisa apenas de argumentos de palavra-chave de uma função, você pode usar o func_defaultsatributo para extraí-los:

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)

Agora você pode chamar sua função com args conhecidos, mas kwargs extraídos, por exemplo:

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

Isso pressupõe que funcnão usa nenhum *argsou **kwargsmagia em sua assinatura.

DzinX
fonte
e se eu quiser imprimir argumentos de "palavra-chave" apenas "chaves"?
Jia,
7

Em 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
fonte
7

Para uma solução Python 3, você pode usar inspect.signaturee filtrar de acordo com o tipo de parâmetros que deseja conhecer.

Obtendo uma função de amostra com parâmetros posicionais ou de palavra-chave, apenas palavra-chave, var posicional e var palavra-chave:

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

Você pode criar um objeto de assinatura para ele:

from inspect import signature
sig =  signature(spam)

e, em seguida, filtre com uma compreensão de lista para descobrir os detalhes de que você precisa:

>>> # 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']

e, da mesma forma, para posições var usando p.VAR_POSITIONALe palavra-chave var com VAR_KEYWORD.

Além disso, você pode adicionar uma cláusula ao if para verificar se existe um valor padrão, verificando se p.defaulté igual p.empty.

Dimitris Fasarakis Hilliard
fonte
3

Ampliando a resposta do 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
fonte