4

I was searching for a way to identify whether some argument is used for unpacking and I have found this:

>>> def func_has_positional_args(func):
    std_args = func.func_code.co_argcount
    wildcard_args = len(func.func_code.co_varnames) - std_args
    if wildcard_args == 2:
        return True  # yes, has both positional and keyword args
    elif wildcard_args == 0:
        return False  # has neither positional, nor keyword args
    else:
        raise NotImplementedError('Unable to tell')


>>> func_has_keyword_args = func_has_positional_args
>>> def test1(a, b, *args, **kwargs): pass

>>> func_has_positional_args(test1), func_has_keyword_args(test1)
(True, True)
>>> def test2(a, b): pass

>>> func_has_positional_args(test2), func_has_keyword_args(test2)
(False, False)
>>> def test3(a, b, *args): pass

>>> func_has_positional_args(test3)

Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    func_has_positional_args(test3)
  File "<pyshell#41>", line 9, in func_has_positional_args
    raise NotImplementedError('Unable to tell')
NotImplementedError: Unable to tell

So I am able to tell, if there are is no positional, nor keyword arguments unpacking. I am also able to tell if there are both, but I am unable to distinguish which "wildcard" type argument is implemented, if there is only one "wildcard".

Could you help me achieve the following result?

# Already satisfied with above code:
assert func_has_positional_args(test1) == True
assert func_has_keyword_args(test1) == True
assert func_has_positional_args(test2) == False
assert func_has_keyword_args(test2) == False

# Missing functionality (tests are failing):
assert func_has_positional_args(test3) == True
assert func_has_keyword_args(test3) == False

Also, does Python 3 change anything with regard to this functionality or its behavior?

Tadeck
  • 132,510
  • 28
  • 152
  • 198
  • 3
    ... I'm not sure I'm following this really closely, but aren't you looking for `inspect.getargspec`? How is what you are doing different? – mgilson Jan 10 '14 at 06:29
  • @mgilson: Yes, bullseye ;) Seemingly I was too focused on looking up function properties instead of looking at `inspect` module... Would you care to post it as an answer? – Tadeck Jan 10 '14 at 06:32

1 Answers1

5

As mgilson commented, use inspect.getargspec (more preferably inspect.getfullargspec in Python 3.x).

import inspect

def func_has_positional_args(func):
    spec = inspect.getfullargspec(func)
    return bool(spec.varargs) # varargs: name of the * argument or None
def func_has_keyword_args(func):
    spec = inspect.getfullargspec(func)
    return bool(spec.varkw)   # varkw: name of the ** argument or None

Example:

>>> def test1(a, b, *args, **kwargs): pass
...
>>> def test2(a, b): pass
...
>>> def test3(a, b, *args): pass
...
>>> func_has_positional_args(test1)
True
>>> func_has_keyword_args(test1)
True
>>> func_has_positional_args(test2)
False
>>> func_has_keyword_args(test2)
False
>>> func_has_positional_args(test3)
True
>>> func_has_keyword_args(test3)
False
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • Thanks, so preferred way for writing Python 2.x + 3.x compatible code is to actually do that during import of `inspect`, right? I did not find `six` module support for this function. – Tadeck Jan 10 '14 at 07:04
  • @Tadeck, If you want the code that run both in Python 2, 3. Use `inspect.getargspec`. (Replace `varkw` with `keywords`). – falsetru Jan 10 '14 at 07:06
  • @Tadeck, `inspect.getargspec` is deprecated according to the documetation. But `getargspec` is still there even in [Python 3.4](http://docs.python.org/3.4/library/inspect.html#inspect.getargspec). – falsetru Jan 10 '14 at 07:08