Skip to content

Workshop: Functions (Optional, Self-Study)

View workshop code on GitHub Get the Video Course

From the lesson

Workshop problems for Section 07 Concepts. Build each yourself first, then expand the solution to check.

Twelve optional workshop problems, one per concept step. The same ideas as the lesson, in a car scenario. The numbers line up with the steps (Workshop-01 goes with Step-01).

Self-study (not live-coded): the stubs say WHAT to do; you write the code, then check python-workshop/solutions.

Step-01: Define and Call a Function

Concepts: def with no parameters, docstrings, calling a function with (), define once and call many times.

Write a function show_banner that takes no arguments, carries the docstring Print the car banner., and prints Welcome to Sam Motors, plus a function divider that takes no arguments and prints ----------. Then call divider, show_banner, and divider again.

Expected output
----------
Welcome to Sam Motors
----------
Need a hint?

Define show_banner (with a docstring) and divider separately, then call them in the order divider, show_banner, divider to frame the banner.

Show the solution
s07_workshop01_sol_define_and_call.py
def show_banner():
    """Print the car banner."""
    print("Welcome to Sam Motors")

def divider():
    print("----------")

divider()
show_banner()
divider()

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-02: One Argument

Concepts: parameters vs arguments, passing a value, passing a variable, positional arguments (left to right).

Write a function show_car that takes a brand and prints Car: <brand>, plus a function car_origin that takes a brand and a country and prints <brand> is made in <country>. Then:

  • Task-1: call show_car with Audi, then store BMW in a variable favorite and call show_car with that variable.
  • Task-2: call car_origin with Toyota and Japan in that order (so the first value fills brand and the second fills country).
Expected output
Car: Audi
Car: BMW
Toyota is made in Japan
Need a hint?

show_car takes one brand; an argument can be a literal ("Audi") or a variable (favorite). car_origin fills brand then country by position.

Show the solution
s07_workshop02_sol_one_argument.py
# Task-1: show a car by value and by variable
def show_car(brand):
    print(f"Car: {brand}")

show_car("Audi")
favorite = "BMW"
show_car(favorite)

# Task-2: show a car origin with positional arguments
def car_origin(brand, country):
    print(f"{brand} is made in {country}")

car_origin("Toyota", "Japan")

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-03: Return (print vs return)

Concepts: return a value to reuse it, and return a True/False answer from a comparison (car service scenario).

Write a function service_cost that takes parts and labor and returns their sum, plus a function is_affordable that takes a price and a budget and returns whether the price is within budget (a True/False answer). Then:

  • Task-1: call service_cost with 200 and 150, store the result in cost, print cost, then print cost plus 50 (a tax fee).
  • Task-2: call is_affordable with 350 and 500 and print the result, then call it with 700 and 500 and print the result.
Expected output
350
400
True
False
Need a hint?

service_cost returns a number you can reuse (cost + 50); is_affordable returns the comparison price <= budget directly as a bool.

Show the solution
s07_workshop03_sol_return.py
# Task-1: return the service cost, then reuse it
def service_cost(parts, labor):
    return parts + labor
cost = service_cost(200, 150)
print(cost)
print(cost + 50)

# Task-2: return a True/False affordability answer
def is_affordable(price, budget):
    return price <= budget
print(is_affordable(350, 500))
print(is_affordable(700, 500))

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-04: Parameters and Logic

Concepts: many parameters (positional), if/elif/else inside a function, a loop inside a function that builds up a total.

  • Task-1: write a function service_cost that takes hours and rate and returns their product (a labor cost), then call it with 3 and 40 and with 2 and 55, printing each result.
  • Task-2: write a function fuel_status that takes liters and returns a word: Full for 40 or more, OK for 15 or more, otherwise Low. Call it with 50, with 20, and with 5, printing each result.
  • Task-3: write a function fleet_distance that takes a list distances and returns the total kilometers driven, adding them up itself (do not use the built-in sum()). Call it with [120, 80, 200] and print the result.
  • Task-4: write a function any_low_tank that takes a list levels and a reserve and returns True the moment it finds the first level below reserve (return right there, do not keep checking), otherwise returns False after the loop. Call it with [60, 10, 45] and 15 and print the result, then call it with [60, 40, 45] and 15 and print the result.
Expected output
120
110
Full
OK
Low
400
True
False
Need a hint?

service_cost returns a product; fuel_status uses if/elif/else from highest threshold down; fleet_distance keeps a running total in a loop and returns it; any_low_tank loops and return True on the first level below reserve, with return False after the loop.

Show the solution
s07_workshop04_sol_parameters_and_logic.py
# Task-1: return hours times rate
def service_cost(hours, rate):
    return hours * rate

print(service_cost(3, 40))
print(service_cost(2, 55))

# Task-2: return a fuel word with if/elif/else
def fuel_status(liters):
    if liters >= 40:
        return "Full"
    elif liters >= 15:
        return "OK"
    else:
        return "Low"

print(fuel_status(50))
print(fuel_status(20))
print(fuel_status(5))

# Task-3: total a list with a loop, no sum()
def fleet_distance(distances):
    running_total = 0
    for distance in distances:
        running_total = running_total + distance
    return running_total

print(fleet_distance([120, 80, 200]))

# Task-4: return True on the first tank below the reserve, else False
def any_low_tank(levels, reserve):
    for level in levels:
        if level < reserve:
            return True   # found one, so stop right here
    return False          # only reached after checking every tank

print(any_low_tank([60, 10, 45], 15))
print(any_low_tank([60, 40, 45], 15))

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-05: Default Parameters and Keyword Arguments

Concepts: default parameter values, keyword arguments (name=value in the call), the mutable-default trap (use None, not []).

  • Task-1: write a function rent_car that takes a model, a days that defaults to 1, and an insurance that defaults to basic, and returns <model>: <days> days, <insurance> insurance. Call it with Swift only (all defaults), then with Creta and days=3, then with Innova and insurance="full", printing each result.
  • Task-2: write a function add_extra that takes an extra and an extras that defaults to None, starts a fresh empty list when extras is None, adds extra to it, and returns it. Call it with GPS and print the result, then call it with child seat and print the result (each call must start with a fresh list).
Expected output
Swift: 1 days, basic insurance
Creta: 3 days, basic insurance
Innova: 1 days, full insurance
['GPS']
['child seat']

Never use [] as a default value

A default like extras=[] is created once and shared across every call, so items pile up between calls. Use extras=None and create a fresh list inside the function instead.

Need a hint?

days=1 and insurance="basic" are optional defaults you can override by keyword; for add_extra use extras=None and a fresh list, never extras=[].

Show the solution
s07_workshop05_sol_default_keyword_args.py
# Task-1: default days and insurance parameters
def rent_car(model, days=1, insurance="basic"):
    return f"{model}: {days} days, {insurance} insurance"

print(rent_car("Swift"))
print(rent_car("Creta", days=3))
print(rent_car("Innova", insurance="full"))

# Task-2: use None default to avoid the mutable-default trap
def add_extra(extra, extras=None):
    if extras is None:
        extras = []
    extras.append(extra)
    return extras

print(add_extra("GPS"))
print(add_extra("child seat"))

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-06: Return Many Values

Concepts: returning several values comma-separated (a tuple), tuple unpacking, min()/max().

Write a function price_range that takes a list prices and returns two values, the cheapest and the most expensive, plus a function price_stats that takes a list prices and returns three values, the cheapest, the most expensive, and how many prices there are. Then, for the list [1200000, 800000, 1500000]:

  • Task-1: call price_range, store the single returned value, and print it (you will see a tuple).
  • Task-2: call price_range again, unpack the two returned values into cheapest and dearest in one line, and print cheapest: 800000 and dearest: 1500000.
  • Task-3: call price_stats, unpack the three returned values into cheapest, dearest, and count in one line, and print cheapest: 800000, dearest: 1500000, and count: 3.
Expected output
(800000, 1500000)
cheapest: 800000
dearest: 1500000
cheapest: 800000
dearest: 1500000
count: 3
Need a hint?

return min(prices), max(prices) returns a two-value tuple; print it whole, or unpack with cheapest, dearest = price_range(...). price_stats adds len(prices) for a third value, unpacked with cheapest, dearest, count = price_stats(...).

Show the solution
s07_workshop06_sol_return_many.py
def price_range(prices):
    return min(prices), max(prices)

def price_stats(prices):
    return min(prices), max(prices), len(prices)

# Task-1: store and print the returned tuple
result = price_range([1200000, 800000, 1500000])
print(result)

# Task-2: unpack the two returned values
cheapest, dearest = price_range([1200000, 800000, 1500000])
print("cheapest:", cheapest)
print("dearest:", dearest)

# Task-3: unpack the three returned values (cheapest, dearest, count)
cheapest, dearest, count = price_stats([1200000, 800000, 1500000])
print("cheapest:", cheapest)
print("dearest:", dearest)
print("count:", count)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-07: args and *kwargs

Concepts: *args gathers prices into a tuple, **kwargs gathers car details into a dict, call-site unpacking with * (list) and ** (dict), keyword-only arguments with a bare *.

Problem-1: gather positional args and unpack a list

Write a function service_total that gathers any number of positional arguments (use *prices) and returns their sum. Then:

  • Task-1: call service_total with 1500, 800, 2300 and print the result.
  • Task-2: define a list repairs holding 500, 250, call service_total with that list unpacked into the call (use *repairs), and print the result.
Expected output
4600
750
Need a hint?

*prices gathers positional arguments into a tuple to sum(); *repairs at the call site spreads a list into separate arguments.

Show the solution
s07_workshop07_sol_args_kwargs.py
def service_total(*prices):
    return sum(prices)
# Problem-1 Task-1: call with three prices
print(service_total(1500, 800, 2300))
# Problem-1 Task-2: unpack a list into the call
repairs = [500, 250]
print(service_total(*repairs))

View the full solution file on GitHub

Problem-2: gather named args and unpack a dict

Write a function show_car that gathers any number of named arguments (use **details) and prints each key: value pair on its own line. Then:

  • Task-1: call show_car with the named arguments brand="Mazda" and year=2020.
  • Task-2: define a dict car mapping brand to Audi and year to 2021, then call show_car with that dict unpacked into the call (use **car).
Expected output
brand: Mazda
year: 2020
brand: Audi
year: 2021
Need a hint?

**details gathers named arguments into a dict to loop with .items(); **car at the call site spreads a dict into named arguments.

Show the solution
s07_workshop07_sol_args_kwargs.py
def show_car(**details):
    for key, value in details.items():
        print(f"{key}: {value}")
# Problem-2 Task-1: call with named arguments
show_car(brand="Mazda", year=2020)
# Problem-2 Task-2: unpack a dict into the call
car = {"brand": "Audi", "year": 2021}
show_car(**car)

View the full solution file on GitHub

Problem-3: keyword-only arguments with a bare *

Write a function price_after_tax that takes keyword-only parameters price and tax (a bare * before them forces callers to pass them by name) and returns their sum. Call it with price=20000 and tax=1800 and print the result.

Expected output
21800
Need a hint?

A bare * before price and tax makes them keyword-only, so you must call price_after_tax(price=20000, tax=1800).

Show the solution
s07_workshop07_sol_args_kwargs.py
# Problem-3: keyword-only price and tax
def price_after_tax(*, price, tax):
    return price + tax
print(price_after_tax(price=20000, tax=1800))

View the full solution file on GitHub

Problem-4: one function with both *args and **kwargs

Write a function make_booking that gathers any number of positional arguments into *extras and any number of named arguments into **details (the *extras must come BEFORE **details), and prints extras: followed by the extras tuple on one line, then details: followed by the details dict on the next line. Call it with the positional arguments GPS and child seat and the named arguments model="Creta" and days=3.

Expected output
extras: ('GPS', 'child seat')
details: {'model': 'Creta', 'days': 3}
Need a hint?

One function can take both: write def make_booking(*extras, **details) with *extras BEFORE **details; the positional arguments land in the extras tuple and the named ones in the details dict.

Show the solution
s07_workshop07_sol_args_kwargs.py
# Problem-4: one function taking both *args and **kwargs (*extras before **details)
def make_booking(*extras, **details):
    print("extras:", extras)
    print("details:", details)
make_booking("GPS", "child seat", model="Creta", days=3)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-08: Scope (LEGB)

Concepts: reading a global, the global statement, enclosing scope, nonlocal.

Problem-1: The global statement

Define a global variable cars_sold set to 0, then write a function sell_car that takes no arguments, declares cars_sold with the global statement, and increases cars_sold by 1. Call sell_car three times, then print cars_sold.

Expected output
3
Need a hint?

global cars_sold lets sell_car change the module-level counter, so three calls reach 3.

Show the solution
s07_workshop08_sol_scope.py
# Problem-1: global statement mutates the global
cars_sold = 0

def sell_car():
    global cars_sold
    cars_sold = cars_sold + 1

sell_car()
sell_car()
sell_car()
print(cars_sold)

View the full solution file on GitHub

Problem-2: Enclosing scope and nonlocal

Write a function garage that starts a local variable count at 0 and holds a nested function park which declares count with nonlocal, increases it by 1, and returns it; inside garage, call park twice and print each result. Then call garage.

Expected output
1
2
Need a hint?

nonlocal count lets the nested park rebind the count in garage, so the two calls return 1 then 2.

Show the solution
s07_workshop08_sol_scope.py
# Problem-2: nonlocal updates the enclosing count
def garage():
    count = 0
    def park():
        nonlocal count
        count = count + 1
        return count
    print(park())
    print(park())

garage()

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-09: First-class and Higher-order Functions

Concepts: a function is a value (store it in a variable), higher-order functions (pass a function as an argument), map/filter over a list, and picking a function from a dict by key (dispatch).

  • Task-1: write a function add_tax that takes a price and returns the price plus 10 percent of it, store the function itself in a variable named with_tax (the name with no parentheses), and call with_tax with 20000 and print the result. Then write a function apply_discount that takes a function func and a price, calls func on price, and returns that result minus 500; call apply_discount with with_tax and 20000 and print the result.
  • Task-2: write a function service_fee that takes km and returns km * 2, and a function needs_service that takes km and returns whether km is above 10000 (a True/False answer). Define a variable mileages as a list with the values 5000, 12000, 8000, 20000. Use map to apply service_fee to every mileage, turn the result into a list, and print it. Then use filter to keep only the mileages that needs_service says need service, turn the result into a list, and print it.
  • Task-3: write a function to_petrol that takes a brand and returns <brand> petrol, and a function to_diesel that takes a brand and returns <brand> diesel. Put both functions in a dict named fuels under the keys petrol and diesel (store the function names, no parentheses). Look up the function under the key diesel, store it in a variable named chosen, call chosen with Audi, and print the result. Then look up the function under the key petrol, call it with BMW, and print the result.
Expected output
22000
21500
[10000, 24000, 16000, 40000]
[12000, 20000]
Audi diesel
BMW petrol

Store the function, do not call it

Write with_tax = add_tax with no parentheses to store the function itself. Writing with_tax = add_tax(20000) would call add_tax and store its result, not the function.

Need a hint?

Task-1: store the function with with_tax = add_tax (no parentheses) and let apply_discount call the function you pass in via func(price). Task-2: map(service_fee, mileages) transforms every mileage and filter(needs_service, mileages) keeps the ones above 10000; wrap each in list(...). Task-3: put the functions in fuels = {"petrol": to_petrol, "diesel": to_diesel} with no parentheses, then pick one by key like fuels["diesel"] and call it.

Show the solution
s07_workshop09_sol_first_class_functions.py
# Task-1: store a function in a variable, and pass a function as an argument
def add_tax(price):
    tax = price * 10 // 100
    return price + tax

with_tax = add_tax          # store the function itself; note: no parentheses
print(with_tax(20000))

def apply_discount(func, price):
    return func(price) - 500

print(apply_discount(with_tax, 20000))

# Task-2: map applies a function to every item, filter keeps the ones that pass a test
def service_fee(km):
    return km * 2

def needs_service(km):
    return km > 10000

mileages = [5000, 12000, 8000, 20000]
print(list(map(service_fee, mileages)))       # apply service_fee to each mileage
print(list(filter(needs_service, mileages)))  # keep only the ones needing service

# Task-3: functions live in a dict and are picked by key (dispatch)
def to_petrol(brand):
    return brand + " petrol"

def to_diesel(brand):
    return brand + " diesel"

fuels = {"petrol": to_petrol, "diesel": to_diesel}  # store the functions, no parentheses
chosen = fuels["diesel"]                             # look up the function by key
print(chosen("Audi"))                                # then call it
print(fuels["petrol"]("BMW"))                        # look up and call in one step

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-10: Lambda

Concepts: lambda, passing a lambda as key= to sorted(), tuple indexing (car[1]), list indexing ([0]).

For the list of (brand, price) pairs ("Audi", 40000), ("Kia", 25000), ("BMW", 50000):

  • Task-1: print the pairs sorted by price (the second item of each pair), using sorted() with a lambda as the key=.
  • Task-2: print the cheapest car (the first item of that sorted list).
  • Task-3: for a second fleet of (brand, price) pairs ("Ford", 30000), ("Tata", 18000), ("Benz", 60000), print the pairs sorted by price, again using sorted() with a lambda as the key=.
Expected output
[('Kia', 25000), ('Audi', 40000), ('BMW', 50000)]
('Kia', 25000)
[('Tata', 18000), ('Ford', 30000), ('Benz', 60000)]
Need a hint?

key=lambda car: car[1] sorts the (brand, price) pairs by price; the first item of the sorted list is the cheapest car. The same key=lambda car: car[1] sorts the second fleet by price too.

Show the solution
s07_workshop10_sol_lambda.py
cars = [("Audi", 40000), ("Kia", 25000), ("BMW", 50000)]
# Task-1: sort cars by price with a lambda key
cars_by_price = sorted(cars, key=lambda car: car[1])
print(cars_by_price)
# Task-2: print the cheapest car
print(cars_by_price[0])
# Task-3: sort a second fleet by price with a lambda key
fleet = [("Ford", 30000), ("Tata", 18000), ("Benz", 60000)]
print(sorted(fleet, key=lambda car: car[1]))

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-11: Modules and Imports

Concepts: import a standard-library module and call module.function(), from module import name, import from your OWN .py module (car theme).

  • Task-1: import math, then apply math.floor to 7.5 and math.sqrt to 144, printing each result.
  • Task-2: from math, import ceil, then apply ceil to 3.2 and print the result.
  • Task-3: create s07_workshop11_cartools.py next to this file holding a price_with_tax function (takes a price and a rate, returns the price plus the price times the rate) and a label function (takes a brand and a model, returns them joined as <brand> <model>). Then, in the workshop file, import both price_with_tax and label from that module, call price_with_tax with 1000 and 0.1 and print the result, and call label with Mazda and CX5 and print the result.
  • Task-4: import the math module under the short alias m and apply m.floor to 6.7, then from math import the name factorial under the alias fact and apply fact to 4, printing each result.
Expected output
7
12.0
4
1100.0
Mazda CX5
6
24
Need a hint?

Mix import math (dot access) with from math import ceil; put price_with_tax and label in s07_workshop11_cartools.py behind an if __name__ == "__main__": guard, then import both names here. For Task-4, import math as m gives the module a short handle (m.floor(6.7)) and from math import factorial as fact renames a single name (fact(4)).

Show the solution
s07_workshop11_sol_modules_imports.py
import math
from math import ceil
import math as m
from math import factorial as fact
from s07_workshop11_cartools import price_with_tax, label

# Task-1: use the math module (floor and sqrt)
# Use the math module on a car.
print(math.floor(7.5))
print(math.sqrt(144))

# Task-2: from math import ceil and use it
# from math import name.
print(ceil(3.2))

# Task-3: import from your own module and call the functions
# Import from your OWN module.
print(price_with_tax(1000, 0.1))
print(label("Mazda", "CX5"))

# Task-4: give a module and a name a short alias with `as`
# import module as alias, and from module import name as alias.
print(m.floor(6.7))
print(fact(4))

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-12: Recursion (bonus)

Concepts: recursion (a function that calls itself), a base case to stop.

Write a recursive function laps_to_go that takes n: when n is 0 it prints Race over! and stops (the base case), otherwise it prints Laps left: <n> and counts down from one less. Call it with 3.

Expected output
Laps left: 3
Laps left: 2
Laps left: 1
Race over!
Need a hint?

Base case if n == 0 prints "Race over!" and returns; otherwise print "Laps left: " and recurse with laps_to_go(n - 1).

Show the solution
s07_workshop12_sol_recursion.py
def laps_to_go(n):
    if n == 0:
        print("Race over!")
        return
    print(f"Laps left: {n}")
    laps_to_go(n - 1)
laps_to_go(3)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Finished this section?

Check your work against the solutions above, then head back to the Section 07 lesson, or try the Section 07 practice for more reps.

Prefer to learn by watching?

The full video course teaches every concept on screen and builds the projects with you, step by step.

Get the Video Course

Workshop: Loops and Comprehensions Next: Workshop: Type Hints

🎉 New Course
Python for Absolute Beginners
$15.99 $84.99 81% OFF
Get the Video Course