Skip to content
VLSI Mentor

Python Part I · Module 11 · Functions

*args and **kwargs

Every function so far has known how many arguments it takes. Some genuinely cannot: a function that submits however many tests you name, a wrapper that passes options through to something else, or print itself, which accepts one value or nine with no change in how you call it. Python has a notation for that, and it is worth learning for two separate reasons. You will occasionally write it. You will constantly read it — the moment you look up a standard-library function, its signature is likely to contain a star, and not being able to read that line means not being able to read the documentation.

Intermediate16 min readPythonPart IFundamentalsFunctionsargs

Part I · Module 11 · Chapter 11.6 · *args and **kwargs

1. A Function That Cannot Know How Many

Submitting three tests with a fixed signature means choosing a number in advance:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def submit(test_a, test_b, test_c):
    return [test_a, test_b, test_c]


print(submit("smoke", "reset", "burst"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'reset', 'burst']

Two tests, or four, and the call fails with Chapter 11.3's TypeError. Passing a list instead works and shifts the brackets onto the caller:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def submit(tests):
    return list(tests)


print(submit(["smoke", "reset", "burst"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['smoke', 'reset', 'burst']

That is a perfectly good design and is often the right one. But print does not make you write print(["a", "b"]), and this is how it avoids it.

2. One Star Collects the Extras

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def submit(*tests):
    print(tests, type(tests))
    return len(tests)


print(submit("smoke", "reset", "burst"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
('smoke', 'reset', 'burst') <class 'tuple'>
3

*tests means collect every remaining positional argument into a tuple called tests. The caller writes a comma-separated list; the function receives one tuple.

Two stars do the same for named arguments, collecting them into a dictionary:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def run(*tests, **options):
    print("tests  :", tests, type(tests))
    print("options:", options, type(options))


run("smoke", "reset", seed=1234, waves=True)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests  : ('smoke', 'reset') <class 'tuple'>
options: {'seed': 1234, 'waves': True} <class 'dict'>

Inside the body, tests is an ordinary tuple and options is an ordinary dictionary. Everything from Modules 6 and 8 applies — index them, loop them, call len() on them, use .items().

3. The Star Is the Syntax; the Name Is Yours

A persistent misconception: args and kwargs are not keywords. They are conventional names, and nothing depends on them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def run(*tests, **options):
    return len(tests), len(options)


print(run("a", "b", seed=1))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
(2, 1)

That function is named *tests and **options throughout this chapter and works identically. The * and ** are the syntax.

Use the convention *args / **kwargs when the arguments are genuinely anonymous pass-through values, and a meaningful name whenever you can think of one. def submit(*tests) tells a reader what those values are; def submit(*args) does not.

4. What a Pass Hands You

Three cases worth knowing exactly, because two of them catch people:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def f(*items):
    return items


print(f())
print(f("a"))
print(f("a", "b"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
()
('a',)
('a', 'b')

No arguments gives an empty tuple, not None. So a *args function never needs a guard before looping — Chapter 10.1 established that an empty collection runs the body zero times.

One argument gives a one-element tuple, with the trailing comma Chapter 6.2 insisted on. f("a") does not hand the body the string "a"; it hands it ("a",). Forgetting that is the usual first bug:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
def shout(*words):
    return words.upper()


print(shout("smoke"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
AttributeError: 'tuple' object has no attribute 'upper'

The body must work on the tuple, not on the value the caller happened to pass once.

5. The Order of the Four Kinds

A signature may contain all four kinds of parameter, and they must appear in this order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def submit(test, *seeds, sim="vcs", **extra):
    return (test, seeds, sim, extra)


print(submit("smoke"))
print(submit("smoke", 1, 2, 3))
print(submit("smoke", 1, 2, sim="xrun", waves=True, timeout=60))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
('smoke', (), 'vcs', {})
('smoke', (1, 2, 3), 'vcs', {})
('smoke', (1, 2), 'xrun', {'waves': True, 'timeout': 60})
PositionKindExampleReceives
1required positionaltestthe first argument
2*name*seedsevery remaining positional, as a tuple
3keyword-onlysim="vcs"only if named
4**name**extraevery remaining named argument, as a dict

Note the third row. Anything after a * is keyword-only — that is the same rule Chapter 11.5 Section 7 got from a bare *, and *seeds does the same job while also collecting. In the last call, sim="xrun" reached its parameter and waves and timeout fell through to extra.

6. The Same Stars Unpack at the Call Site

This is the half most people meet later than they should. In a call, * and ** do the reverse: they spread a collection out into separate arguments.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def run_test(name, seed, sim):
    return name + " " + str(seed) + " " + sim


args = ["smoke", 4210, "vcs"]
print(run_test(*args))

opts = {"name": "reset", "seed": 99, "sim": "xrun"}
print(run_test(**opts))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 4210 vcs
reset 99 xrun

run_test(*args) means take the items of args and pass them as separate positional arguments — it is run_test("smoke", 4210, "vcs"). And run_test(**opts) passes each key-value pair as a named argument.

Note that run_test has an ordinary signature with no stars in it. Unpacking is a property of the call, not of the function.

A mismatch is an ordinary TypeError, because unpacking happens before the call is matched:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
def run_test(name, seed, sim):
    return name


args = ["smoke", 4210]
print(run_test(*args))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: run_test() missing 1 required positional argument: 'sim'

Two items unpacked into three parameters — the same message Chapter 11.3 produced from a hand-written call, because by the time Python matched arguments to parameters there was no difference.

7. Reading print's Signature

Here is the payoff for reading rather than writing. Python documents print as:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(*objects, sep=' ', end='\n')

Every piece of that is now readable:

  • *objects — any number of positional arguments, collected into a tuple. That is why print("a", "b", "c") works with no brackets.
  • sep=' ' — keyword-only, because it follows the *; default a single space.
  • end='\n' — keyword-only; default a newline.

So the behaviour you have relied on since Chapter 1.8 follows from a signature you can now decode:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("a", "b", "c")
print("a", "b", "c", sep="-")
print("a", end=" | ")
print("b")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a b c
a-b-c
a | b

sep is what puts the spaces between the three values, and end is what normally puts the newline on. Neither could have been supplied positionally, because both sit after the *.

8. When Not to Use It

A star in your own signature should be a deliberate choice, not a way to avoid deciding:

  • def f(*args) when the function needs exactly two things removes the TypeError that would have told the caller they got it wrong. The error was the useful part.
  • **kwargs as a dumping ground hides what a function accepts. A reader cannot tell from def run(**options) whether timeout is a real option or a typo that will be silently ignored.
  • A list parameter is often clearer. def submit(tests) called as submit(["a", "b"]) says the same thing as *tests and keeps the signature honest about there being one input.

The strong cases are genuine variadics (print, max) and pass-through wrappers that must forward whatever they were given without inspecting it — which is exactly where you will meet the notation in other people's code.

9. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
def f(*items):
    return len(items), items

print("A:", f(), f(1), f(1, 2))

# B
def g(**opts):
    return sorted(opts)

print("B:", g(b=1, a=2))

# C
def h(first, *rest):
    return first, rest

print("C:", h("a", "b", "c"))

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

pair = [3, 4]
print("D:", add(*pair))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: (0, ()) (1, (1,)) (2, (1, 2))
B: ['a', 'b']
C: ('a', ('b', 'c'))
D: 7

A — empty tuple, one-element tuple with its comma, two-element tuple.

B — looping or sorting a dictionary gives its keys (Chapter 10.3), so sorted(opts) is the key names.

C — first takes one argument and *rest collects what is left. This is the common split-the-head shape.

D — unpacking at the call site into an ordinary two-parameter function.

10. Exercises

Exercise 1 — collect them. Write total(*numbers) that adds any quantity of numbers and returns the sum, using the accumulator pattern from Chapter 10.10. Verify with zero, one and four arguments.

Exercise 2 — the one-element trap. Write a *words function that tries to call .upper() on its parameter directly. Record the error, then fix it so it uppercases each word.

Exercise 3 — name them well. Rewrite each signature with a meaningful name in place of the convention: (a) def submit(*args) for test names (b) def configure(**kwargs) for tool options (c) def log(*args) for message parts.

Exercise 4 — the four kinds. Write one signature containing a required positional, a *-collector, a keyword-only option and a **-collector. Call it three ways and print what each parameter received.

Exercise 5 — unpack. Given job = {"name": "smoke", "seed": 4210, "sim": "vcs"} and a function run_test(name, seed, sim), call it without writing any of the three values out. Then do the same from a list.

Exercise 6 — collect or spread. For each, say which meaning the star has: (a) def f(*x): (b) f(*items) (c) def g(**o): (d) g(**config).

Exercise 7 — decode a signature. Explain each part of def join(sep, *parts, strip=False): in one sentence, then write a call that supplies three parts and sets strip.

Exercise 8 — argue against. For each, say whether *args is the right choice and why: (a) a function that compares exactly two files (b) a function that prints a banner from any number of words (c) a wrapper that forwards all its arguments to another function (d) a function that takes a test name and a seed.

11. Summary

  • *name in a definition collects every remaining positional argument into a tuple; **name collects every remaining named argument into a dict.
  • The * is the syntax; args and kwargs are only conventional names. Use a meaningful name when you have one.
  • No arguments gives an empty tuple, not None — so no guard is needed before looping.
  • One argument gives a one-element tuple, which is the usual first bug: the body must work on the tuple.
  • The four parameter kinds have a fixed order: required positional, *-collector, keyword-only, **-collector.
  • Anything after a * is keyword-only, whether the star is bare or named.
  • At a call site the same stars do the reverse — f(*items) spreads a list into separate arguments, f(**opts) spreads a dict into named ones.
  • Unpacking is a property of the call, not of the function being called.
  • print(*objects, sep=' ', end='\n') is now readable, and explains the spacing and the newline you have relied on since Module 1.
  • Prefer an explicit list parameter unless the function is a genuine variadic or a pass-through wrapper.

The next chapter looks at what actually arrives when the argument is a list.

Where this fits

Part of the Python curriculum.