Explain *args and **kwargs, and how Python passes arguments to functions.
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. The names are convention — the * and ** are what matter.
def f(a, b=2, *args, key=None, **kwargs):
...They work in the other direction too — f(*my_list, **my_dict) unpacks a sequence and a mapping into the call.
How Python passes arguments is the deeper half of this question. It is neither pass-by-value nor pass-by-reference but pass-by-object-reference: the function receives a reference to the same object.
- If the object is mutable and you mutate it, the caller sees the change:
lst.append(1)affects the original. - If you rebind the parameter, the caller sees nothing:
lst = [9]only changes the local name. - Immutable objects — int, str, tuple — cannot be mutated at all, so they always behave as if copied.
Note: A bare * in a signature forces everything after it to be keyword-only, as in def f(a, *, verbose=False). It is a good habit for boolean flags, since f(x, True) at the call site tells the reader nothing.





