In dynamic Python applications (such as plugin systems, decorators, or frameworks) you often need to determine programmatically whether a specific function accepts certain arguments before calling it. Calling a function with unexpected arguments results in a TypeError.
This guide explores how to inspect function signatures using the standard inspect module to validate parameters, check for default values, and safely handle argument passing.
Understanding Function Signatures
A function signature defines the inputs (parameters) and output (return annotation) of a function. Python's inspect module allows you to access this metadata at runtime.
A signature object contains a collection of Parameters, which hold vital details:
- Name: The name of the argument (e.g.,
x,user_id). - Default: The default value if one exists (or
inspect._empty). - Kind: How the argument can be passed (Positional, Keyword,
*args,**kwargs). - Annotation: Type hints (e.g.,
int,str).
Method 1: Using inspect.signature (Recommended)
The most robust way to analyze arguments is by retrieving the Signature object. This allows you to view all expected parameters.
import inspect
def calculate_total(price: float, tax: float = 0.05) -> float:
return price * (1 + tax)
sig = inspect.signature(calculate_total)
print(f"Function Signature: {sig}")
for name, param in sig.parameters.items():
print(f"Name: {name}, Default: {param.default}, Annotation: {param.annotation}")Output:
Function Signature: (price: float, tax: float = 0.05) -> float
Name: price, Default: <class 'inspect._empty'>, Annotation: <class 'float'>
Name: tax, Default: 0.05, Annotation: <class 'float'>Method 2: Checking for Specific Parameter Names
If you simply need to know, "Does this function accept an argument named user_id?", you can check for membership in the parameters dictionary.
Checking for Exact Arguments
import inspect
def send_email(email, subject, body):
pass
def send_sms(phone, message):
pass
def has_argument(func, arg_name):
sig = inspect.signature(func)
return arg_name in sig.parameters
print(f"send_email accepts 'email'? {has_argument(send_email, 'email')}")
print(f"send_sms accepts 'email'? {has_argument(send_sms, 'email')}")Output:
send_email accepts 'email'? True
send_sms accepts 'email'? FalseHandling **kwargs
A function might not explicitly name an argument but still accept it via **kwargs (variable keyword arguments). You should check for VAR_KEYWORD if you want to be strictly accurate about what can be passed.
import inspect
def dynamic_func(a, **kwargs):
pass
sig = inspect.signature(dynamic_func)
params = sig.parameters
arg_name = "random_arg"
accepts_arg = (arg_name in params) or \
any(p.kind == p.VAR_KEYWORD for p in params.values())
print(f"dynamic_func accepts '{arg_name}'? {accepts_arg}")Output:
dynamic_func accepts 'random_arg'? TrueMethod 3: Runtime Checking with try-except
Sometimes, introspection is unnecessary overhead. The Pythonic "Easier to Ask for Forgiveness than Permission" (EAFP) approach involves trying to call the function and catching the TypeError if the arguments don't match.
This is useful when you are ready to execute the function immediately.
def add_numbers(x, y):
return x + y
try:
add_numbers(x=5, y=10, z=15)
except TypeError as e:
print(f"Call failed: {e}")
try:
result = add_numbers(5, 10)
print(f"Result: {result}")
except TypeError as e:
print(f"Error: {e}")Output:
Call failed: add_numbers() got an unexpected keyword argument 'z'
Result: 15Conclusion
To check if a function accepts certain arguments:
- Use
inspect.signature(func)for static analysis or validation logic. It is the most accurate and safe method. - Check
sig.parameterskeys to see if specific argument names exist. - Check for
VAR_KEYWORD(**kwargs) if you need to support dynamic arguments. - Use
try-except TypeErrorif you simply want to call the function and handle mismatches at runtime.