Programmatically determining amount of parameters a function requires – Python

Question:

I was creating a simple command line utility and using a dictionary as a sort of case statement with key words linking to their appropriate function. The functions all have different amount of arguments required so currently to check if the user entered the correct amount of arguments needed for each function I placed the required amount inside the dictionary case statement in the form {Keyword:(FunctionName, AmountofArguments)}.

This current setup works perfectly fine however I was just wondering in the interest of self improval if there was a way to determine the required number of arguments in a function and my google attempts have returned so far nothing of value but I see how args and kwargs could screw such a command up because of the limitless amount of arguments they allow.

Asked By: tomeedee

||

Answers:

inspect.getargspec():

Get the names and default values of a function’s arguments. A tuple of four things is returned: (args, varargs, varkw, defaults). args is a list of the argument names (it may contain nested lists). varargs and varkw are the names of the * and ** arguments or None. defaults is a tuple of default argument values or None if there are no default arguments; if this tuple has n elements, they correspond to the last n elements listed in args.

Answered By: gimel

What you want is in general not possible, because of the use of varargs and kwargs, but inspect.getargspec (Python 2.x) and inspect.getfullargspec (Python 3.x) come close.

  • Python 2.x:

    >>> import inspect
    >>> def add(a, b=0):
    ...     return a + b
    ...
    >>> inspect.getargspec(add)
    (['a', 'b'], None, None, (0,))
    >>> len(inspect.getargspec(add)[0])
    2
    
  • Python 3.x:

    >>> import inspect
    >>> def add(a, b=0):
    ...     return a + b
    ...
    >>> inspect.getfullargspec(add)
    FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(0,), kwonlyargs=[], kwonlydefaults=None, annotations={})
    >>> len(inspect.getfullargspec(add).args)
    2
    
Answered By: Stephan202

Make each command a class, derived from an abstract base defining the general structure of a command. As much as possible, the definition of command properties should be put into class variables with methods defined in the base class handling that data.

Register each of these subclasses with a factory class.
This factory class get the argument list an decides which command to execute by instantiating the appropriate command sub class.

Argument checking is handled by the command sub classes themselves, using properly defined general methods form the command base class.

This way, you never need to repeatedly code the same stuff, and there is really no need to emulate the switch statement. It also makes extending and adding commands very easy, as you can simply add and register a new class. Nothing else to change.

Answered By: Ber

Excellent question. I just had the problem that I wanted to write a function that takes a callback argument. Depending on the number of arguments of that callback, it needs to be called differently.

I started with gimel’s answer, then expanded to be able to deal with builtins which don’t react well with the inspect module (raise TypeError).

So here’s code to check if a function expects exactly one argument:

def func_has_one_arg_only(func, typical_argument=None, ignore_varargs=False):
    """True if given func expects only one argument

    Example (testbench):
    assert not func_has_one_arg_only(dict.__getitem__), 'builtin 2 args'
    assert func_has_one_arg_only(lambda k: k), 'lambda 1 arg'
    assert not func_has_one_arg_only(lambda k,x: k), 'lambda 2 args'
    assert not func_has_one_arg_only(lambda *a: k), 'lambda *a'
    assert not func_has_one_arg_only(lambda **a: k), 'lambda **a'
    assert not func_has_one_arg_only(lambda k,**a: k), 'lambda k,**a'
    assert not func_has_one_arg_only(lambda k,*a: k), 'lambda k,*a'

    assert func_has_one_arg_only(lambda k: k, ignore_varargs=True), 'lambda 1 arg'
    assert not func_has_one_arg_only(lambda k,x: k, ignore_varargs=True), 'lambda 2 args'
    assert not func_has_one_arg_only(lambda *a: k, ignore_varargs=True), 'lambda *a'
    assert not func_has_one_arg_only(lambda **a: k, ignore_varargs=True), 'lambda **a'
    assert func_has_one_arg_only(lambda k,**a: k, ignore_varargs=True), 'lambda k,**a'
    assert func_has_one_arg_only(lambda k,*a: k, ignore_varargs=True), 'lambda k,*a'
    """

    try:
        import inspect
        argspec = inspect.getargspec(func)
    except TypeError:                   # built-in c-code (e.g. dict.__getitem__)
        try:
            func(typical_argument)
        except TypeError:
            return False
        else:
            return True
    else:
        if not ignore_varargs:
            if argspec.varargs or argspec.keywords:
                return False
        if 1 == len(argspec.args):
            return True
        return False
    raise RuntimeError('This line should not be reached')

You can control the behaviour related to varargs arguments *args and **kwargs with the ignore_varargs parameter.

The typical_argument parameter is a kludge: If inspect fails to work, e.g. on the aforementioned builtins, then we just try to call the function with one argument and see what happens.

The problem with this approach is that there are multiple reasons to raise TypeError: Either the wrong number of arguments is used, or the wrong type of arguments is used. By allowing the user to provide a typical_argument I’m trying to circumvent this issue.

This is not nice. But it may help folks having the same question and also running into the fact that inspect cannot inspect C-coded function implementations. Maybe folks have a better suggestion?

Answered By: cfi

This has already been answered but without the inspect module you can also use someMethod.func_code.co_argcount

Answered By: Josep Valls

In Python 3, use someMethod.__code__.co_argcount

(since someMethod.func_code.co_argcount doesn’t work anymore)

Answered By: Bobort
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.