Layer 1 Session 09 Python Foundations

Unpacking and *args/**kwargs

Python has compact syntax for pulling values out of collections, and for functions that accept a flexible number of arguments. Both patterns appear constantly in real code.

(...) abc

One collection, split into several named pieces.

Estimated time: 35โ€“40 minutes  ยท  Pre-quiz โ†’ Concept โ†’ Lab โ†’ Commit โ†’ Post-quiz

1. Learning Objective

By the end of this session you will be able to:

  • Unpack values from a tuple or list into named variables
  • Unpack selected keys from a dictionary
  • Use * to collect "the rest" of a sequence during unpacking
  • Write a function that accepts *args and **kwargs and explain what each collects
  • Use ** to spread a dictionary into a function call as keyword arguments

2. Pre-Coding Quiz

Answer these before reading the concept explanation or writing any code. It is fine to get these wrong โ€” that is the point. You need 4/5 to proceed to the lab.

Question 1 of 5

Given name, region = "Kenya", "Africa", what is region?

Python unpacks the right-hand tuple ("Kenya", "Africa") into the two names on the left, in order. name gets "Kenya", region gets "Africa".
Question 2 of 5

Given first, *rest = [1, 2, 3, 4], what is rest?

The * collects "everything else" into a list. first takes the first item (1), and rest takes everything remaining as a list: [2, 3, 4].
Question 3 of 5

Given def total(*args): return sum(args), what does total(1, 2, 3) return, and what is args inside the function?

*args collects any number of positional arguments into a tuple named args inside the function. sum((1, 2, 3)) is 6.
Question 4 of 5

Given def describe(**kwargs): return kwargs, what does describe(name="Kenya", region="Africa") return?

**kwargs collects any number of keyword arguments into a dictionary. The keys are the argument names, the values are what was passed.
Question 5 of 5

You have data = {"name": "Kenya", "region": "Africa"} and a function def show(name, region): .... Which call passes both dict values as the correct named arguments?

**data spreads the dictionary's key-value pairs as keyword arguments โ€” equivalent to show(name="Kenya", region="Africa"). *data would instead iterate over the dict's keys, which is not what we want here.

3. The Concept โ€” Unpacking and *args / **kwargs

POSITIONALtotal(1, 2, 3)args = (1, 2, 3)KEYWORDdescribe(name=..)kwargs = {name: ..}

*args gathers positional extras into a tuple; **kwargs gathers keyword extras into a dict.

Unpacking a tuple or list

You can assign multiple variables from a sequence in one line, as long as the number of names matches the number of values.

name, region = "Kenya", "Africa"
print(name)    # "Kenya"
print(region)  # "Africa"

# Works with lists too, and with more than 2 values
first, second, third = [10, 20, 30]
print(second)  # 20

Collecting the rest with *

A single starred name absorbs however many values are left over, always as a list.

first, *rest = [1, 2, 3, 4]
print(first)  # 1
print(rest)   # [2, 3, 4]

first, *middle, last = [1, 2, 3, 4, 5]
print(middle)  # [2, 3, 4]
print(last)    # 5

Unpacking selected keys from a dictionary

You cannot unpack a dict positionally like a tuple, but you can pull out specific values using .get() or bracket access โ€” this is how you'll turn a raw dict into named locals in later sessions.

country = {"name": "Kenya", "region": "Africa", "population": 54000000}
name = country["name"]
region = country["region"]
print(name, region)  # Kenya Africa

*args โ€” a function that accepts any number of positional arguments

Inside the function, args is a tuple containing everything the caller passed positionally.

def total(*args):
    print(type(args), args)
    return sum(args)

print(total(1, 2, 3))     # <class 'tuple'> (1, 2, 3)  -> 6
print(total(10, 20))       # <class 'tuple'> (10, 20)   -> 30

**kwargs โ€” a function that accepts any number of keyword arguments

Inside the function, kwargs is a dictionary of every keyword argument the caller passed.

def describe(**kwargs):
    print(type(kwargs), kwargs)
    return kwargs

describe(name="Kenya", region="Africa")
# <class 'dict'> {'name': 'Kenya', 'region': 'Africa'}

Spreading a dict into a call with **

Going the other direction: if you have a dictionary and a function that expects named parameters, ** unpacks it into matching keyword arguments.

def show(name, region):
    print(f"{name} is in {region}")

data = {"name": "Kenya", "region": "Africa"}
show(**data)  # equivalent to show(name="Kenya", region="Africa")

4. Lab

Lab objective: Practice unpacking, *rest collection, and write functions using *args and **kwargs, including spreading a dict into a call.

What you will build

A file called unpacking_lab.py.

Step-by-step instructions

1

Create the file and unpack a country tuple

# unpacking_lab.py
country_tuple = ("Kenya", "Africa", 54000000)
name, region, population = country_tuple
print(name, region, population)
2

Use * to split a list of country names into first and rest

names = ["Kenya", "Ghana", "Peru", "Japan"]
first, *rest = names
print("First:", first)
print("Rest:", rest)
3

Write a total_population function using *args

Call it with 2 numbers and then with 4, proving it works with any count.

def total_population(*populations):
    return sum(populations)

print(total_population(54000000, 31000000))
print(total_population(54000000, 31000000, 33000000, 125000000))
4

Write a build_country function using **kwargs

Print the kwargs dict, then return it.

def build_country(**fields):
    print("Received fields:", fields)
    return fields

country = build_country(name="Kenya", region="Africa", population=54000000)
print(country["name"])
5

Spread a dict into a function call

Write a plain function with named parameters and call it using ** on a dict.

def summarize(name, region, population):
    return f"{name} ({region}) โ€” pop. {population:,}"

data = {"name": "Kenya", "region": "Africa", "population": 54000000}
print(summarize(**data))

5. Expected Files Changed

FileActionWhy
unpacking_lab.py Created Demonstrates unpacking, *rest, *args, **kwargs, and ** spreading.
docs/sessions/session-09/index.html Created This session document.
If you find yourself editing any other file, stop. This session touches exactly 2 files.

6. Commit Checkpoint

Once the lab is complete and you can explain every line, make this exact commit:

git add unpacking_lab.py docs/sessions/session-09/index.html
git commit -m "session-09: unpack sequences and use *args/**kwargs for flexible functions"
Do not commit until you can answer out loud: "What is the type of args inside a function that uses *args, and what is the type of kwargs inside one that uses **kwargs?"

7. Code Review Checklist

Go through your code line by line and check each item:


8. Post-Coding Quiz

Same 5 questions. Take it again now that you have written and run the code. You need 4/5 to mark this session complete.

Question 1 of 5

Given name, region = "Kenya", "Africa", what is region?

Python unpacks the right-hand tuple ("Kenya", "Africa") into the two names on the left, in order. name gets "Kenya", region gets "Africa".
Question 2 of 5

Given first, *rest = [1, 2, 3, 4], what is rest?

The * collects "everything else" into a list. first takes the first item (1), and rest takes everything remaining as a list: [2, 3, 4].
Question 3 of 5

Given def total(*args): return sum(args), what does total(1, 2, 3) return, and what is args inside the function?

*args collects any number of positional arguments into a tuple named args inside the function. sum((1, 2, 3)) is 6.
Question 4 of 5

Given def describe(**kwargs): return kwargs, what does describe(name="Kenya", region="Africa") return?

**kwargs collects any number of keyword arguments into a dictionary. The keys are the argument names, the values are what was passed.
Question 5 of 5

You have data = {"name": "Kenya", "region": "Africa"} and a function def show(name, region): .... Which call passes both dict values as the correct named arguments?

**data spreads the dictionary's key-value pairs as keyword arguments โ€” equivalent to show(name="Kenya", region="Africa"). *data would instead iterate over the dict's keys, which is not what we want here.

9. Reflection Questions

Think through these after the post-quiz. No right answer โ€” they are for discussion.

  1. Why does *args produce a tuple while **kwargs produces a dictionary? Does that difference make sense given how positional vs keyword arguments work?
  2. What would happen if you tried to unpack a, b = [1, 2, 3] โ€” three values into two names? Try it and read the error.
  3. Where in the labs so far have you already used dictionary values without realizing it was a form of unpacking?
  4. Can you think of a real function signature (in any library you've used) that probably uses **kwargs internally?

10. What Breaks If This Knowledge Is Missing?

  • Unpacking mismatches: Unpacking the wrong number of values (too many or too few names) raises a ValueError at runtime. Understanding this session means you'll immediately recognise that error instead of being confused by it.
  • Flexible constructors (Layer 2 and 4): When we build classes and a mock-data layer, functions frequently need to accept a variable, evolving set of fields. **kwargs is exactly how you keep a function's signature stable while its data shape grows.
  • Calling real library functions (Layer 7): The requests library and many others accept **kwargs-style configuration. Without this session, those function calls look like unexplainable magic.

11. What We Learned

Python concept mastered: Unpacking sequences, the * "rest" pattern, and *args/**kwargs for functions with flexible argument counts.

Unlocks: You can now write functions that accept a flexible, evolving set of inputs โ€” essential for the data layer we build in Layer 4.

Next session: Session 10 โ€” Modules and Imports. We will split our growing file into separate modules and learn Python's import system.