23

I'm looking for a way to check the number of arguments that a given function takes in Python. The purpose is to achieve a more robust method of patching my classes for tests. So, I want to do something like this:

class MyClass (object):
    def my_function(self, arg1, arg2):
        result = ... # Something complicated
        return result

def patch(object, func_name, replacement_func):
    import new

    orig_func = getattr(object, func_name)
    replacement_func = new.instancemethod(replacement_func, 
                           object, object.__class__)

    # ...
    # Verify that orig_func and replacement_func have the 
    # same signature.  If not, raise an error.
    # ...

    setattr(object, func_name, replacement_func)

my_patched_object = MyClass()
patch(my_patched_object, "my_function", lambda self, arg1: "dummy result")
# The above line should raise an error!

Thanks.

2
  • "patching my classes for tests"? Why aren't you using mock objects? python-mock.sourceforge.net? Commented Aug 20, 2010 at 21:34
  • 3
    I'm new to using mocks. I "grew up" stubbing and patching. I'm getting practice and figuring out when to use which, but in the mean time, I still have projects to finish, and tests to write :). Commented Aug 21, 2010 at 22:40

6 Answers 6

22

inspect.getargspec is deprecated in Python 3. Consider something like:

import inspect
len(inspect.signature(foo).parameters)

This will include args and kwargs as one parameter, each.

If you don't want to include args and kwargs, consider:

len(inspect.getfullargspec(foo)[0])
Sign up to request clarification or add additional context in comments.

Comments

16

You can use:

import inspect
len(inspect.getargspec(foo_func)[0])

This won't acknowledge variable-length parameters, like:

def foo(a, b, *args, **kwargs):
    pass

1 Comment

Now, it should be inspect.getfullargspec(foo)[0] for new Python version.
5

You should use inspect.getargspec.

Comments

2

The inspect module allows you to examine a function's arguments. This has been asked a few times on Stack Overflow; try searching for some of those answers. For example:

Getting method parameter names in python

2 Comments

I see. I did a bit of searching before posting, but I guess I should have use more search terms. Sorry for the bother.
No problem. Better to point to you to the other answers, though, than just repeat everything here.
0

Using the accepted answer you can even construct the entire type like this:

import inspect
from typing import Callable, Any, Type, List

def ftype(func: Callable[[Any], Any]) -> Type[Callable[[Any], Any]]:
    assert callable(func)
    ret = inspect.signature(func).return_annotation
    params = list(inspect.signature(func).parameters.values())
    param_types: List[Type[Any]] = [param.annotation for param in params]
    return Callable[[*param_types], ret]

In your code you can then check the type with the is keyword like this:

def equal_strings(text1: str, text2: str) -> bool:
    return text1 == text2

# ...

>>> my_fuction is Callable[[str, str], bool] 
True

>>> ftype(equal_strings)
typing.Callable[[str, str], bool]

However mypy doesn't like any of this, which is probably what lead you here in the first place.

I just thought this piece of magic was interesting

Comments

0

I have written my own small library that solves this problem with less client code, it's called sigmatch.

Install it:

pip install sigmatch

And use:

from sigmatch import SignatureMatcher

def function(a, b, c=5, *d, **e):
    ...

matcher = SignatureMatcher('.', '.', 'c', '*', '**')
print(matcher.match(function))  # True

In this example, the expected parameters of the function are fixed in the SignatureMatcher object, and the match() method checks the function for compliance with them. That's what the SignatureMatcher initialization parameters mean:

  • "." - corresponds to an ordinary positional arguments without a default value.
  • "some_argument_name" - corresponds to an argument with a default value. The content of the string is the name of the argument.
  • "*" - corresponds to packing multiple positional arguments without default values (*args).
  • "**" - corresponds to packing several named arguments with default values (**kwargs).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.