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")?

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top