Obtendo nomes de parâmetro método em Python
-
03-07-2019 - |
Pergunta
Dada a função Python:
def aMethod(arg1, arg2):
pass
Como posso extrair o número e os nomes dos argumentos. Ou seja, uma vez que eu tenho uma referência para func, eu quero o func. [Algo] para voltar ( "arg1", "arg2").
O cenário de uso para isso é que eu tenho um decorador, e gostaria de usar os argumentos de método na mesma ordem em que aparecem para a função real como uma chave. Ou seja, como é que o olhar decorador que imprimiu "a, b" quando eu chamo aMethod ( "a", "b")?
Solução
Dê uma olhada na inspecionar módulo - isso vai fazer a inspeção dos vários propriedades do objeto código para você.
>>> inspect.getfullargspec(aMethod)
(['arg1', 'arg2'], None, None, None)
Os demais resultados são o nome dos args * e variáveis ??kwargs **, e os padrões fornecidos. ie.
>>> def foo(a,b,c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))
Note que algumas funções chamáveis ??pode não ser introspectable em determinadas implementações de Python. Por exemplo, em CPython, algumas funções internas definidas em C fornecer nenhuma metadados sobre os seus argumentos. Como resultado, você obterá ValueError
no caso de você usar inspect.getfullargspec()
com um built-in função.
Desde Python 3.3, você pode usar também o inspect.signature () a fim de conhecer a assinatura chamada de um objeto que pode ser chamado:
>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Outras dicas
Em CPython, o número de argumentos é
aMethod.func_code.co_argcount
e seus nomes estão no início de
aMethod.func_code.co_varnames
Estes são detalhes de implementação de CPython, então isso provavelmente não funciona em outras implementações de Python, como IronPython e Jython.
Uma maneira portátil para admitir "pass-through" argumentos é definir a função com o func(*args, **kwargs)
assinatura. Isto é muito usada em e.g. matplotlib, onde a camada de API externa passa lotes de argumentos nomeados para a API de nível inferior.
Em um método decorador, você pode listar argumentos do método original da seguinte maneira:
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)
Se o **kwargs
são importantes para você, então ele vai ser um pouco 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()
Exemplo:
@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]
Aqui está algo que eu acho que vai trabalhar para o que você quer, usando um 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")
executá-lo, ele irá produzir o seguinte 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.
Eu acho que o que você está procurando é o método moradores locais -
In [6]: def test(a, b):print locals()
...:
In [7]: test(1,2)
{'a': 1, 'b': 2}
A versão Python 3 é:
def _get_args_dict(fn, args, kwargs):
args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
return {**dict(zip(args_names, args)), **kwargs}
O método retorna um dicionário que contém ambos os args e kwargs.
Python 3.5 +:
DeprecationWarning: inspect.getargspec () está obsoleto, o uso inspect.signature () em vez
Assim anteriormente:
func_args = inspect.getargspec(function).args
Agora:
func_args = list(inspect.signature(function).parameters.keys())
Para teste:
'arg' in list(inspect.signature(function).parameters.keys())
Dado que temos a função 'função', que leva argumento arg ', este será avaliada como verdadeira, caso contrário, como False.
Exemplo do console 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
Em Python 3. + com o objeto Signature
na mão, uma maneira fácil de obter um mapeamento entre nomes de argumentos com os valores, é usando o método bind()
da assinatura!
Por exemplo, aqui é um decorador para imprimir um mapa assim:
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}
Retorna uma lista de nomes de argumentos, cuida de parciais e funções regulares:
def get_func_args(f):
if hasattr(f, 'args'):
return f.args
else:
return list(inspect.signature(f).parameters)
Atualização para de Brian resposta :
Se uma função em Python 3 tem palavra-chave únicos argumentos, então você precisa usar inspect.getfullargspec
:
def yay(a, b=10, *, c=20, d=30):
pass
inspect.getfullargspec(yay)
produz o seguinte:
FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})
pitão 3, abaixo é fazer *args
e **kwargs
num dict
(uso OrderedDict
para python <3,6 para manter ordens dict
):
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 atualizar um pouco de Brian resposta , há agora uma boa backport de inspect.signature
que você pode usar em python mais velhos versões: funcsigs
.
Assim, a minha preferência pessoal iria para
try: # python 3.3+
from inspect import signature
except ImportError:
from funcsigs import signature
def aMethod(arg1, arg2):
pass
sig = signature(aMethod)
print(sig)
Para se divertir, se você estiver interessado em jogar com objetos Signature
e até mesmo a criação de funções com assinaturas aleatórias dinamicamente você pode dar uma olhada no meu makefun
projeto.
E sobre dir()
e vars()
agora?
Parece fazendo exatamente o que está sendo solicitado Super simplesmente ...
Deve ser chamado de dentro do escopo da função.
Mas muito cuidado que ela retorne todas variáveis ??locais isso não deixe de fazê-lo no início da função, se necessário.
Observe também que, como fora apontado nos comentários, isso não permitir que ele seja feito a partir de fora do escopo. Então não é exatamente o cenário de OP, mas ainda corresponde ao título da pergunta. Daí a minha resposta.