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.
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
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_carwithAudi, then storeBMWin a variablefavoriteand callshow_carwith that variable. - Task-2: call
car_originwithToyotaandJapanin that order (so the first value fillsbrandand the second fillscountry).
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
# 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")
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_costwith200and150, store the result incost, printcost, then printcostplus 50 (a tax fee). - Task-2: call
is_affordablewith350and500and print the result, then call it with700and500and print the result.
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
# 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))
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_costthat takeshoursandrateand returns their product (a labor cost), then call it with3and40and with2and55, printing each result. - Task-2: write a function
fuel_statusthat takeslitersand returns a word:Fullfor 40 or more,OKfor 15 or more, otherwiseLow. Call it with50, with20, and with5, printing each result. - Task-3: write a function
fleet_distancethat takes a listdistancesand returns the total kilometers driven, adding them up itself (do not use the built-insum()). Call it with[120, 80, 200]and print the result. - Task-4: write a function
any_low_tankthat takes a listlevelsand areserveand returnsTruethe moment it finds the first level belowreserve(return right there, do not keep checking), otherwise returnsFalseafter the loop. Call it with[60, 10, 45]and15and print the result, then call it with[60, 40, 45]and15and print the result.
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
# 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))
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_carthat takes amodel, adaysthat defaults to1, and aninsurancethat defaults tobasic, and returns<model>: <days> days, <insurance> insurance. Call it withSwiftonly (all defaults), then withCretaanddays=3, then withInnovaandinsurance="full", printing each result. - Task-2: write a function
add_extrathat takes anextraand anextrasthat defaults toNone, starts a fresh empty list whenextrasisNone, addsextrato it, and returns it. Call it withGPSand print the result, then call it withchild seatand print the result (each call must start with a fresh list).
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
# 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"))
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_rangeagain, unpack the two returned values intocheapestanddearestin one line, and printcheapest: 800000anddearest: 1500000. - Task-3: call
price_stats, unpack the three returned values intocheapest,dearest, andcountin one line, and printcheapest: 800000,dearest: 1500000, andcount: 3.
(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
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)
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_totalwith1500, 800, 2300and print the result. - Task-2: define a list
repairsholding500, 250, callservice_totalwith that list unpacked into the call (use*repairs), and print the result.
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
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_carwith the named argumentsbrand="Mazda"andyear=2020. - Task-2: define a dict
carmappingbrandtoAudiandyearto2021, then callshow_carwith that dict unpacked into the call (use**car).
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
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)
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.
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
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.
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
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.
Need a hint?
global cars_sold lets sell_car change the module-level counter, so three calls reach 3.
Show the solution
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.
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
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_taxthat takes apriceand returns the price plus 10 percent of it, store the function itself in a variable namedwith_tax(the name with no parentheses), and callwith_taxwith20000and print the result. Then write a functionapply_discountthat takes a functionfuncand aprice, callsfunconprice, and returns that result minus 500; callapply_discountwithwith_taxand20000and print the result. - Task-2: write a function
service_feethat takeskmand returnskm * 2, and a functionneeds_servicethat takeskmand returns whetherkmis above 10000 (aTrue/Falseanswer). Define a variablemileagesas a list with the values5000,12000,8000,20000. Usemapto applyservice_feeto every mileage, turn the result into a list, and print it. Then usefilterto keep only the mileages thatneeds_servicesays need service, turn the result into a list, and print it. - Task-3: write a function
to_petrolthat takes abrandand returns<brand> petrol, and a functionto_dieselthat takes abrandand returns<brand> diesel. Put both functions in a dict namedfuelsunder the keyspetrolanddiesel(store the function names, no parentheses). Look up the function under the keydiesel, store it in a variable namedchosen, callchosenwithAudi, and print the result. Then look up the function under the keypetrol, call it withBMW, and print the result.
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
# 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
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 alambdaas thekey=. - 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 usingsorted()with alambdaas thekey=.
[('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
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]))
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 applymath.floorto7.5andmath.sqrtto144, printing each result. - Task-2: from
math, importceil, then applyceilto3.2and print the result. - Task-3: create
s07_workshop11_cartools.pynext to this file holding aprice_with_taxfunction (takes a price and a rate, returns the price plus the price times the rate) and alabelfunction (takes a brand and a model, returns them joined as<brand> <model>). Then, in the workshop file, import bothprice_with_taxandlabelfrom that module, callprice_with_taxwith1000and0.1and print the result, and calllabelwithMazdaandCX5and print the result. - Task-4: import the
mathmodule under the short aliasmand applym.floorto6.7, then frommathimport the namefactorialunder the aliasfactand applyfactto4, printing each result.
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
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))
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.
Need a hint?
Base case if n == 0 prints "Race over!" and returns; otherwise print "Laps left: laps_to_go(n - 1).
Show the solution
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.
Workshop: Loops and Comprehensions Next: Workshop: Type Hints