Python Function Call: Syntax, Binding, and Behavior
python function call: How Python evaluates a function call, binds positional and keyword arguments, handles return values, and what counts as callable.
What Happens When You Call a Function
A python function call is an expression. When the interpreter evaluates func(arg1, arg2), it performs several steps in a fixed order: it evaluates func to obtain a callable object, evaluates each argument expression from left to right, binds the resulting values to the function's parameters, executes the function body, and finally produces a value for the call expression itself.
The evaluation order is deterministic and worth remembering. Arguments are evaluated before the function body runs, and they are evaluated left to right. In func(compute_a(), compute_b()), compute_a() runs first, then compute_b(), and only then does the body of func execute. If your argument expressions have side effects, the order is guaranteed by the language, not an implementation detail.
Function Call Syntax: Positional and Keyword Arguments
Python accepts two forms of arguments in a call: positional and keyword.
def connect(host, port, timeout=30): ... connect("db.internal", 5432) connect(host="db.internal", port=5432, timeout=10)
Positional arguments are matched to parameters in declaration order. Keyword arguments are matched by parameter name, which makes the call self-documenting and removes the need to remember the exact parameter order. The two forms can be mixed, but the parser requires positional arguments to appear before keyword arguments:
connect("db.internal", port=5432)
Writing connect(host="db.internal", 5432) raises a SyntaxError. This rule is enforced at parse time, not at runtime.
Argument Binding Rules and Defaults
When a call is executed, Python binds values to parameters in a specific order. Positional arguments fill parameters from left to right. Keyword arguments fill the remaining parameters by name. Parameters with default values receive their default when the call does not supply a value. Any extra positional arguments are collected into *args, and any extra keyword arguments are collected into **kwargs.
def log(message, level="info", *tags, **context): ...
The declaration side has its own constraint: a parameter with a default cannot be followed by a parameter without one. This is a signature rule, enforced when the function is defined, and it exists because a parameter without a default would have no way to receive a value if an earlier optional parameter were omitted.
Return Behavior: The Implicit None
Every python function call produces a value. If the function body ends without a return statement, or executes a bare return, the call evaluates to None. This is a frequent source of bugs: a function that is meant to return a computed result but forgets the return statement silently returns None.
def normalize(value): normalized = value.strip().lower() # missing return result = normalize(" Hello ") # result is None
The call itself does not fail; the bug only appears when the caller uses the result. This is why functions that intentionally return nothing are often annotated with -> None.
Callable Objects and What Python Treats as Callable
The call syntax obj(...) works on any object that is callable. This includes functions defined with def, lambda expressions, methods bound to an instance, classes themselves, and instances of classes that define __call__.
class Retry: def __init__(self, limit): self.limit = limit def __call__(self, fn): ...
Calling a class constructs an instance, which is why ClassName() is the standard construction syntax. The callable() built-in reports whether an object can be called, which is useful before invoking an object whose type is not known at compile time.
Common Call Mistakes and Edge Cases
Mutable default arguments are bound once, at function definition time, not per call. A default list or dictionary is therefore shared across every call that omits that argument.
def add_item(item, items=[]): items.append(item) return items
Each call that omits items mutates the same list, so results accumulate across calls. The standard fix is to use None as the default and create a fresh collection inside the body.
Keyword arguments are matched by name, so a typo in a keyword name raises a TypeError rather than being silently ignored. This is usually desirable, but it means you cannot pass arbitrary keyword names to a function unless it accepts **kwargs.
Runtime Cost of Function Calls
Each python function call creates a new frame on the call stack, binds arguments into that frame, and tears the frame down when the call completes. The overhead is negligible for a single call, but it becomes measurable in tight loops that invoke a function millions of times. Attribute lookups add further cost because the interpreter must resolve the method or attribute on the type.
def total(values): return sum(values) for value in large_list: result = total([value])
Wrapping a trivial operation in a function and calling it per iteration adds overhead that could be avoided by inlining the logic. The practical guidance is not to avoid functions, but to avoid calling a small helper millions of times inside a hot loop when the helper only wraps a single built-in operation. The tradeoff is between readability and call overhead, and for most application code the readability wins.