Question: I want to validate (at runtime, this is not a typing question), that a function passed as an argument takes only 1 positional variable (basically that function will be called with a string as input and returns a truthy). Naively, this is what I had: def check(v_in : Callable): """check that the function can be called with 1 positional parameter supplied""" sig = signature(v_in) len_param = len(sig.parameters) if not len_param == 1: raise ValueError( f"expecting 1 parameter, of type `str` for {v_in}. got {len_param}" ) return v_in  If I check the following function, it is OK, which is good: def f_ok1_1param(v : str): pass  but the next fails, though the *args would receive 1 param just fine and **kwargs would be empty def f_ok4_vargs_kwargs(*args,**kwargs): pass  from rich import inspect as rinspect try: check(f_ok1_1param) print("\n\npasses:") rinspect(f_ok1_1param, title=False,docs=False) except (Exception,) as e: print("\n\nfails:") rinspect(f_ok1_1param, title=False,docs=False) try: check(f_ok4_vargs_kwargs) print("\n\npasses:") rinspect(f_ok4_vargs_kwargs, title=False,docs=False) except (Exception,) as e: print("\n\nfails:") rinspect(f_ok4_vargs_kwargs, title=False,docs=False)  which passes the first, and fails the second, instead of passing both: passes: ╭─────────── <function f_ok1_1param at 0x1013c0f40> ───────────╮ │ def f_ok1_1param(v: str): │ │ │ │ 37 attribute(s) not shown. Run inspect(inspect) for options. │ ╰──────────────────────────────────────────────────────────────╯ fails: ╭──────── <function f_ok4_vargs_kwargs at 0x101512200> ────────╮ │ def f_ok4_vargs_kwargs(*args, **kwargs): │ │ │ │ 37 attribute(s) not shown. Run inspect(inspect) for options. │ ╰──────────────────────────────────────────────────────────────╯  all the different combination of signatures are defined below: def f_ok1_1param(v : str): pass def f_ok2_1param(v): pass def f_ok3_vargs(*v): pass def f_ok4_p_vargs(p, *v): pass def f_ok4_vargs_kwargs(*args,**kwargs): pass def f_ok5_p_varg_kwarg(param,*args,**kwargs): pass def f_bad1_2params(p1, p2): pass def f_bad2_kwargs(**kwargs): pass def f_bad3_noparam(): pass  Now, I did already check a bit more about the Parameters: rinspect(signature(f_ok4_vargs_kwargs).parameters["args"]) ╭─────────────────── <class 'inspect.Parameter'> ───────────────────╮ │ Represents a parameter in a function signature. │ │ │ │ ╭───────────────────────────────────────────────────────────────╮ │ │ │ <Parameter "*args"> │ │ │ ╰───────────────────────────────────────────────────────────────╯ │ │ │ │ KEYWORD_ONLY = <_ParameterKind.KEYWORD_ONLY: 3> │ │ kind = <_ParameterKind.VAR_POSITIONAL: 2> │ │ name = 'args' │ │ POSITIONAL_ONLY = <_ParameterKind.POSITIONAL_ONLY: 0> │ │ POSITIONAL_OR_KEYWORD = <_ParameterKind.POSITIONAL_OR_KEYWORD: 1> │ │ VAR_KEYWORD = <_ParameterKind.VAR_KEYWORD: 4> │ │ VAR_POSITIONAL = <_ParameterKind.VAR_POSITIONAL: 2> │ ╰───────────────────────────────────────────────────────────────────╯  I suppose checking Parameter.kind vs _ParameterKind enumeration of each parameter is how this needs to be approached, but I wonder if I am overthinking this or if something already exists to do this, either in inspect or if the typing protocol support can be used to do, but at runtime. Note, theoretically def f_ok_cuz_default(p, p2 = None): would also work, but let's ignore that for now. p.s. The motivation is providing a custom callback function in a validation framework. The call location is deep in the framework and that particular argument can also be a string (which gets converted to a regex). It can even be None. Easiest here is just to stick a def myfilter(*args,**kwargs): breakpoint. Or myfilter(foo). Then look at what you get from the framework and adjust body. It’s one thing to have exceptions in your function, another for the framework to accept it but then error before calling into it. So a quick “will this work when we call it?” is more user friendly.

Answer: I don't think your problem is trivial, at all. And I am not aware of any given implementation, so I followed your train of thoughts and came to the conclusion that in the end, the problem boils down to answering the following questions:  Does the callable have any arguments, at all? If so, is the first argument positional? If so, are all other arguments optional?  If you answer all questions with yes, then your function can be called with exactly one (positional) argument, otherwise not. In this context,  positional means:  a single positional-or-keyword argument, as in f(a), or a single positional-only argument, as in f(a, /), or a variable list of positional arguments, as in f(*args);   optional means:  a variable list of positional arguments, as in f(a, *args), or a variable mapping of keyword arguments, as in f(a, **kwargs), or an argument with a default value, as in f(a, b=None).    Implementation You can implement the corresponding check as follows: from inspect import Parameter, signature from typing import Callable def _is_positional(param: Parameter) -> bool: return param.kind in [ Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL] def _is_optional(param: Parameter) -> bool: return (param.kind in [ Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD] or param.default is not Parameter.empty) def has_one_positional_only(fct: Callable) -> bool: args = list(signature(fct).parameters.values()) return (len(args) > 0 and # 1. We have one or more args _is_positional(args[0]) and # 2. First is positional all(_is_optional(a) for a in args[1:])) # 3. Others are optional  Test cases This will return the correct results for your test cases (which I extended a bit): def f_ok1_1param(v : str): pass def f_ok2_1param(v): pass def f_ok3_vargs(*v): pass def f_ok4_p_vargs(p, *v): pass def f_ok4_vargs_kwargs(*args, **kwargs): pass def f_ok5_p_varg_kwarg(param,*args,**kwargs): pass def f_ok6_pos_only(v, /): pass # also ok: explicitly positional only def f_ok7_defaults(p, d=None): pass # also ok: with default value def f_bad1_2params(p1, p2): pass def f_bad2_kwargs(**kwargs): pass def f_bad3_noparam(): pass def f_bad4_kwarg_after_args(*args, v): pass # also not ok: v after *args is keyword-only def f_bad5_kwarg_only(*, v): pass # also not ok: explicitly keyword-only print(has_one_positional_only(f_ok1_1param)) # True print(has_one_positional_only(f_ok2_1param)) # True print(has_one_positional_only(f_ok3_vargs)) # True print(has_one_positional_only(f_ok4_p_vargs)) # True print(has_one_positional_only(f_ok5_p_varg_kwarg)) # True print(has_one_positional_only(f_ok6_pos_only)) # True print(has_one_positional_only(f_ok7_defaults)) # True print(has_one_positional_only(f_bad1_2params)) # False print(has_one_positional_only(f_bad2_kwargs)) # False print(has_one_positional_only(f_bad3_noparam)) # False print(has_one_positional_only(f_bad4_kwarg_after_args)) # False print(has_one_positional_only(f_bad5_kwarg_only)) # False  Two final thoughts:  At first I thought that the 2nd question needs to be formulated more generally, namely: Is any argument positional? However, I did not come up with any combination where it is not the first argument that must be the positional one (according to the given definition of positional, that is), and I am quite confident it is not possible with current Python syntax rules. The solution for sure is not the most efficient one, given that with the knowledge about the parameters that you already checked, certain others are impossible to follow and thus actually don't need to be checked again (e.g. if the first optional argument was **kwargs then an optional *args cannot follow, any more).  Update for clarification: The provided solution will "work" in the sense that it checks whether a given callable's signature is compatible with calling it with exactly one positional argument. What it cannot ensure is that one given positional argument satisfies the internal logic of the callable. For example, if f(*args) internally tries to access an element after the 1st element in args, it will fail if called with one argument only, even though the proposed check lets it pass. There is not much to do to check the internal logic from the outside this way, other than running the callable or inspecting its actual code. (Also, to my understanding, the latter is not asked for in the question.)