Коротко

*args собирает позиционные аргументы в кортеж, **kwargs — именованные в словарь. Удобно для обёрток, декораторов и гибких API.

def log(level, *args, **kwargs):
    print(level, args, kwargs)

log("INFO", "start", user="ann")
# INFO ('start',) {'user': 'ann'}

Распаковка при вызове:

def add(a, b):
    return a + b

pair = (1, 2)
add(*pair)

Порядок параметров

обычные, затем *args, затем keyword-only, затем **kwargs.

Короткий ответ

«*args — лишние позиционные в tuple, **kwargs — лишние именованные в dict. Нужны для гибких функций и проксирования аргументов.»