How to get the set of all Python arguments?

Asked

Viewed 350 times

7

Python has both positional and named parameters and operators * and ** which allow receiving an arbitrary number of additional arguments:

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

foo(1, 2)          # 1, 2, 1, (), {}
foo(1, 2, 3)       # 1, 2, 3, (), {}
foo(1, 2, 3, 4)    # 1, 2, 3, (4,), {}
foo(1, 2, 3, 4, 5) # 1, 2, 3, (4,5), {}

foo(b=1, a=2)                # 2, 1, 1, (), {}
foo(b=1, a=2, c=3)           # 2, 1, 3, (), {}
foo(b=1, a=2, c=3, d=4)      # 2, 1, 3, (), {'d':4}
foo(b=1, a=2, c=3, d=4, e=5) # 2, 1, 3, (), {'d':4, 'e':5}

foo(1, 2, 3, 4, 5, d=6, e=7) # 1 2 3 (4, 5) {'d': 6, 'e': 7}

I would like to know whether, in such a case, you mix explicitly stated parameters with lists/sets of arbitrary arguments, whether it is possible to obtain the set of all of them, and not just the additional ones. Example:

def foo(a, b, c=1, *args, **kwargs):
    print(list_args(...))
    print(dict_args(...))

foo(1, 2, 3, 4, 5, d=6, e=7)
# (1, 2, 3, 4, 5, 6, 7)
# {'a':1, 'b':2, 'c':3, 3:4, 4:5, 'd':6, 'e':7}

(only one example; such a feature could have additional restrictions - such as not mixing *args with **kwargs, or represent the arguments in a different way - but the interesting thing is that something like this existed/could be done)

Is it possible? Compare with the Javascript language, which allows both named parameters and access to the list of all arguments via arguments:

function foo(a, b, c) {
    console.log(arguments);
}

foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
                    // Repare que o conjunto inclui "a", "b" e "c", ao contrário do Python

Note: the motivation of this question is to find a way to create a function that has a well defined set of parameters (with exact number and names, and maybe optional values) but can pass on all of them (after some validation, or even change) to another function that has exactly the same parameters.

  • I swear I still don’t understand the motivation of this. If it’s all accurate, why do you need to do this kind of validation?

  • @Palaces The motivation is to assist in the creation of decorators (the design pattern, not necessarily the syntactic construction). A practical example - not necessarily the best - would be to create a signature function identical to a model in Django, validate the data received (business rules) and then pass it on to objects.create without having to explicitly list each argument.

2 answers

2


You can do:

import inspect

def func(a, b, c):
    frame = inspect.currentframe() #equivalente a sys._getframe(0)
    args_names, _, _, locals_ = inspect.getargvalues(frame)
    args = [locals_[i] for i in args_names]
    kwargs = dict(zip(args_names, args))
    print "args: %r" %args
    print "kwargs: %r" %kwargs


>>> func(1, 2, 3)
args: [1, 2, 3]
kwargs: {'a': 1, 'c': 3, 'b': 2}

>>> func(c=1, a=2, b=3)
args: [2, 3, 1]
kwargs: {'a': 2, 'c': 1, 'b': 3}

You can do a function that dump arguments:

import sys
import inspect

def dumpargs():
    frame = sys._getframe(1) # 0: funcao atual, 1: pega a funcao anterior na pilha
    args_names, _, _, locals_ = inspect.getargvalues(frame)
    args = [locals_[i] for i in args_names]
    kwargs = dict(zip(args_names, args))
    return args, kwargs


def outra(a, b, c, d=1):
    args, kwargs = dumpargs()
    print "kwargs: %r" %kwargs
    print "args: %r" %args

>>> outra(1, 2, 3)
kwargs: {'a': 1, 'c': 3, 'b': 2, 'd': 1}
args: [1, 2, 3, 1]

>>> outra(1, 2, 3, 4)
kwargs: {'a': 1, 'c': 3, 'b': 2, 'd': 4}
args: [1, 2, 3, 4]

>>> outra(b=1, c=2, d=3, a=4)
kwargs: {'a': 4, 'c': 2, 'b': 1, 'd': 3}
args: [4, 1, 2, 3]

Update 01

As noted by the questioner, part of this code is only guaranteed to work on Cpython. sys._getframe, for example, it is specific to Cpython no guarantee to work on Pypy, Jython or other python implementation. Also, inspect.getargvalues is deprecated since python 3.5

Update 02

The code works in PyPy 2.6.0 (python 2.7.9) and in the Jython 2.7.0

  • 1

    Very good! Some of these functions are obsolete (deprecated), and others are implementation dependent, but I tested this code on Cpython 3.4 and it worked correctly. Some adjustments may be interesting depending on the case (for example, I notice that you ignored two values in the getargvalues, so that he disregards *args and **kwargs), but overall everything seems ok.

  • @mgibsonbr getargvalues is obsolete even (since python 3.5), but you did not speak the python version. I also have not yet been interested in programming for python3k, although I am well aware of the new implementation and changes. I haven’t done it yet because most of the code I support still depends on python 2.5 (neither is 2.7)

  • I also continue to work on 2.7 (a lot of legacy code that would be hard to migrate), and also restricted to Cpython, so your answer is perfect for me. But I thought it important to leave this note in case someone who uses Python 3.5+ or Jython, Pypy, Ironpython etc is aware of the limitations (because also the currentframe may not be available, as well as the sys._getframe).

  • 1

    I tested it, it works on PyPy 2.6.0 (python 2.7.9) and in the Jython 2.7.0. Does not work in the IronPython 2.7.5, neither implements nor sys._getframe nor inspect.getargvalues

  • Test https://stackoverflow.com/a/10974508/1522342 instead of sys._getframe(1)

1

I’m not sure what you really want, however I believe I have a simpler approach.

Tried calling locals right at the start of the function?

def foo(a, b, c=1, *args, **kw):
    assinatura = locals()
    print(assinatura)


In [5]: foo(1, 2, 3, 4, 5, d=6, e=7)
{'kw': {'d': 6, 'e': 7}, 'args': (4, 5), 'a': 1, 'b': 2, 'c': 3}

If you need anything more precise, I suggest you use the method getfullargspec module inspect. It will return all the information about the function parameters, which can be compared with the result of locals.

  • I do not think there is much difference between your answer and his initial proposal (contained in the question): print(a, b, c, args, kwargs) but avoid writing each argument.

  • @iuridiniz If you need something more precise, I suggest you use the getfullargspec method of the Inspect module. It will return all the information about the function parameters, which can be compared with the locals result which, in the end, gives in the same way as your answer, the exception is not to use impaired functions. Also, if you want something fast, I believe that locals be a simple shortcut.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.