38

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
tomeedee
  • 1,209
  • 1
  • 12
  • 11
  • I posted another solution here that works even when the function has keyword-only parameters without defaults or positional-only parameters with defaults. https://stackoverflow.com/a/74328772/9318372 – Hyperplane Nov 05 '22 at 14:56

6 Answers6

45

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.

gimel
  • 83,368
  • 10
  • 76
  • 104
  • Thanks I just discovered that very function as you posted that answer. Thats exactly what I need. – tomeedee Apr 12 '09 at 15:50
  • Also since I don't have the rep to edit your answer you have typo in your answer it's getargspec() not getargspect() – tomeedee Apr 12 '09 at 15:54
  • 1
    Should use inspect.getfullargspec(func) (http://docs.python.org/3.1/library/inspect.html#inspect.getfullargspec) for Python 3.x – Casebash Oct 24 '09 at 13:05
  • 4
    Better yet: use `Signature` objects, introduced in Python 3.3. See https://docs.python.org/3/library/inspect.html#inspect-signature-object for documentation. They would correctly avoid counting the `self` parameter when working on bounded method objects. – Giulio Piancastelli Jun 21 '14 at 18:51
18

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
    
Stephan202
  • 59,965
  • 13
  • 127
  • 133
  • 4
    `inspect.getargspec(function)` is a named tuple, so instead of `len(inspect.getargspec(function)[0])` you can use the more readable `len(inspect.getargspec(function).args)`. – 1'' Oct 29 '14 at 03:32
  • 1
    `getargspec` has been deprecated since Python 3, use `signature` or `getfullargspec` instead. – Akshay Aug 13 '16 at 23:30
  • @1'': That is true as of version 2.6, it wasn't in the older version I used back then. – Stephan202 Aug 22 '16 at 19:45
  • 1
    @akshay: Thanks for the heads up. I added a Python 3.x example (using 1"'s named tuple suggestion). – Stephan202 Aug 22 '16 at 19:47
4

In Python 3, use someMethod.__code__.co_argcount

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

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Bobort
  • 3,085
  • 32
  • 43
  • this has already been answered 3 months ago. Check the other answers before answering (and of course don't copy other answers) – Jean-François Fabre Nov 02 '16 at 20:37
  • 1
    I was looking for an answer to this question, and none of the listed ones were satisfactory to me. I found this answer elsewhere and placed it here in case any one comes searching for this issue through Google. I very much did check the other answers. I did not copy any answer. – Bobort Nov 02 '16 at 21:31
  • look at the answer Josep Valls did the 4th of august: "This has already been answered but without the inspect module you can also use `someMethod.func_code.co_argcount`". Pretty similar ain't it? maybe that's why it was flagged for moderator attention. – Jean-François Fabre Nov 02 '16 at 22:57
  • It is similar, but it is NOT the same. It does NOT work for Python 3. `func_code` is not an attribute in Python 3 on functions. Instead, you use `__code__`. A more advanced user of Python would reasonably understand that the two versions have some major compatibility changes that can be frustrating at times to deal with. This is one such change. – Bobort Nov 03 '16 at 15:32
  • that is correct. I'm editing your answer so I can remove the downvote. – Jean-François Fabre Nov 03 '16 at 15:35
2

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

Josep Valls
  • 5,483
  • 2
  • 33
  • 67
1

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.

Ber
  • 40,356
  • 16
  • 72
  • 88
  • 2
    +1; You, sir, are funny. Given that you wrote this after the accepted answer had been posted, I'm assuming you are intentionally expressing very cogent views on defensive programming rather than attempting to describe the easiest way to count how many args the function takes. – user1612868 May 29 '14 at 10:17
0

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?

cfi
  • 10,915
  • 8
  • 57
  • 103