Python ยท Lesson 15

Python Functions, Arguments, *args / **kwargs, and Scope

8 phases  ยท  Build: Project Goal

๐Ÿ‘‹ Welcome to Lesson 15

Imagine you are baking a cake. Every time a customer orders one, you don't invent a brand-new recipe from scratch ยท you follow the same recipe each time, maybe swapping the flavour depending on what the customer wants. That recipe is a function.

In Python, a function is a reusable block of code that you write once and run as many times as you need, with different values each time if required. Functions are the single most important building block of any real program. Without them, you would copy and paste the same code over and over ยท messy, error-prone, and hard to fix.

This lesson covers four closely connected topics from the W3Schools Python tutorials, merged into one smooth, progressive lesson:

TopicWhat You Will Learn
FunctionsHow to create and call functions
ArgumentsHow to pass values into functions
\args and \\*kwargsHow to handle an unknown number of arguments
ScopeWhere variables live and who can see them

By the end of this lesson you will be able to write your own functions, pass data into them flexibly, and fully understand why variables sometimes seem to "disappear" ยท and why that is actually a good thing.


๐Ÿ“š 8 phases๐Ÿ—๏ธ Project Goal๐Ÿ GitHub Repo
Phase 1 of 8
Lesson Introduction

Imagine you are baking a cake. Every time a customer orders one, you don't invent a brand-new recipe from scratch ยท you follow the same recipe each time, maybe swapping the flavour depending on what the customer wants. That recipe is a function.

In Python, a function is a reusable block of code that you write once and run as many times as you need, with different values each time if required. Functions are the single most important building block of any real program. Without them, you would copy and paste the same code over and over ยท messy, error-prone, and hard to fix.

This lesson covers four closely connected topics from the W3Schools Python tutorials, merged into one smooth, progressive lesson:

TopicWhat You Will Learn
FunctionsHow to create and call functions
ArgumentsHow to pass values into functions
\args and \\*kwargsHow to handle an unknown number of arguments
ScopeWhere variables live and who can see them

By the end of this lesson you will be able to write your own functions, pass data into them flexibly, and fully understand why variables sometimes seem to "disappear" ยท and why that is actually a good thing.


โœ๏ธ Your Task
Practise what you just learned about Lesson Introduction. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 2 of 8
Prerequisite Concepts

Before we begin, here are the concepts you must already understand. If any of these feel unfamiliar, take a few minutes to review them.

  • Variables ยท storing a value in a name (e.g., x = 5)
  • print() ยท displaying output in the terminal
  • Basic data types ยท strings ("hello"), integers (5), floats (3.14), lists ([1, 2, 3])
  • if / else ยท making decisions in code
  • for loops ยท repeating an action multiple times

No prior experience with functions or scope is needed.


โœ๏ธ Your Task
Practise what you just learned about Prerequisite Concepts. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 3 of 8
Part 1 ยท Python Functions

1.1 What Is a Function?

A function is a named, reusable block of code. You define it once, and then you can call (run) it as many times as you like from anywhere in your program.

Real-world analogy: Think of a function like a vending machine button. The machine (your function) is built once. Every time someone pushes the button (calls the function), the machine does the same job and delivers a result.

Why do functions exist?

  • DRY principle ยท "Don't Repeat Yourself." Write the logic once, use it everywhere.
  • Readability ยท Code broken into named functions is far easier to read and understand.
  • Maintenance ยท If a rule changes, you fix it in one place instead of hunting through hundreds of lines.
  • Testing ยท Small, focused functions are much easier to test and debug.

1.2 Defining a Function

In Python, you create a function using the def keyword.

Syntax:

python
def function_name():
    # code that runs when the function is called

Breaking down the syntax line by line:

PartWhat It Means
defShort for "define" ยท tells Python you are creating a function
function_nameThe name you give your function (use lowercase and underscores)
()Parentheses ยท this is where inputs (arguments) will go later
:The colon signals the start of the function body
Indented blockAll code indented underneath belongs to this function

1.3 Your First Function

python
def say_hello():
    print("Hello from inside a function!")

Important: Defining a function does not run it. It just stores the recipe. Nothing appears on screen yet.


1.4 Calling a Function

To actually run the function, you call it by writing its name followed by parentheses:

python
def say_hello():
    print("Hello from inside a function!")

say_hello()

Expected output:

code
Hello from inside a function!

You can call the same function multiple times:

python
say_hello()
say_hello()
say_hello()

Expected output:

code
Hello from inside a function!
Hello from inside a function!
Hello from inside a function!

๐Ÿ’ก Think about it: What happens if you never call the function? Try removing say_hello() from the code. What do you see?


1.5 Why Indentation Matters

Python uses indentation (spaces at the start of a line) to know which lines belong inside a function. This is not optional ยท it is the rule.

python
def greet():
    print("Line 1 โ€“ inside the function")
    print("Line 2 โ€“ also inside the function")

print("Line 3 โ€“ OUTSIDE the function, runs immediately")
greet()

Expected output:

code
Line 3 โ€“ OUTSIDE the function, runs immediately
Line 1 โ€“ inside the function
Line 2 โ€“ also inside the function

๐Ÿง  Notice: Line 3 runs first because it is outside the function and sits at the top level of the program. Line 1 and Line 2 only run when greet() is called.


1.6 Common Beginner Mistake ยท Forgetting the Call

python
# โŒ WRONG โ€“ defines the function but never calls it
def show_score():
    print("Your score is 100")

# Nothing is printed because we forgot to call it!

Corrected version:

python
# โœ… CORRECT
def show_score():
    print("Your score is 100")

show_score()  # This line actually runs the function

Expected output:

code
Your score is 100

1.7 Common Beginner Mistake ยท Indentation Error

python
# โŒ WRONG โ€“ missing indentation inside function body
def greet():
print("Hello")  # IndentationError!

Corrected version:

python
# โœ… CORRECT
def greet():
    print("Hello")  # 4 spaces of indentation

1.8 The pass Statement

Sometimes you want to create a function skeleton now and fill in the logic later. An empty function body causes a syntax error ยท use pass as a placeholder:

python
def my_future_function():
    pass  # Does nothing, but Python is happy

my_future_function()  # No output, no error

This is common in professional development when planning out a project structure before writing all the logic.


โœ๏ธ Your Task
Practise what you just learned about Python Functions. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 4 of 8
Part 2 ยท Arguments and Parameters

2.1 What Are Arguments?

So far our functions do the exact same thing every time. But real functions need to work with different data each time ยท like a calculator that can add any two numbers, not just 2 + 2.

Parameters and arguments let you pass information into a function.

The difference:

WordMeaningWhere
ParameterThe variable name listed in the function definitionInside def parentheses
ArgumentThe actual value you pass when calling the functionInside the call parentheses

Think of a parameter as an empty slot, and an argument as the value you drop into that slot.


2.2 A Function with One Parameter

python
def greet_person(name):       # 'name' is the parameter
    print("Hello, " + name + "!")

greet_person("Alice")         # "Alice" is the argument
greet_person("Bob")           # "Bob" is the argument
greet_person("Dr. Johnson")   # "Dr. Johnson" is the argument

Expected output:

code
Hello, Alice!
Hello, Bob!
Hello, Dr. Johnson!

๐Ÿง  What changed? The same function ran three times. Each time, the name parameter held a different value. The function code never changed ยท only the input did.


2.3 A Function with Multiple Parameters

You can define as many parameters as you need, separated by commas:

python
def describe_student(first_name, subject, grade):
    print(first_name + " studies " + subject + " and scored " + str(grade) + "%.")

describe_student("Maria", "Maths", 94)
describe_student("James", "Biology", 78)

Expected output:

code
Maria studies Maths and scored 94%.
James studies Biology and scored 78%.

Note: str(grade) converts the number 94 into the string "94" so it can be joined with the other strings using +. This is called type conversion.


2.4 Positional Arguments

When you pass arguments in order, Python matches them to parameters by position:

python
def show_order(item, quantity, price):
    print(item + " ร— " + str(quantity) + " = ยฃ" + str(price))

show_order("Apples", 6, 3.00)
# item = "Apples", quantity = 6, price = 3.00

Expected output:

code
Apples ร— 6 = ยฃ3.0

โš ๏ธ Order matters! If you mix up the order, you get incorrect results ยท or an error:

python
show_order(6, "Apples", 3.00)   # quantity and item are swapped!

Incorrect output:

code
6 ร— Apples = ยฃ3.0

2.5 Keyword Arguments

You can also pass arguments using the parameter name so order does not matter:

python
def show_order(item, quantity, price):
    print(item + " ร— " + str(quantity) + " = ยฃ" + str(price))

show_order(quantity=6, price=3.00, item="Apples")

Expected output:

code
Apples ร— 6 = ยฃ3.0

Using keyword arguments makes your code self-documenting ยท anyone reading it knows exactly what each value means.


2.6 Default Parameter Values

You can give a parameter a default value. If the caller does not provide that argument, the default is used automatically:

python
def greet(name, language="English"):
    if language == "English":
        print("Hello, " + name + "!")
    elif language == "Spanish":
        print("Hola, " + name + "!")
    elif language == "French":
        print("Bonjour, " + name + "!")

greet("Alice")               # uses default: English
greet("Carlos", "Spanish")   # overrides default
greet("Marie", "French")     # overrides default

Expected output:

code
Hello, Alice!
Hola, Carlos!
Bonjour, Marie!

๐Ÿ’ก Rule: Parameters with defaults must come after parameters without defaults in the function definition.

python
# โŒ WRONG
def greet(language="English", name):   # SyntaxError
    pass

# โœ… CORRECT
def greet(name, language="English"):
    pass

2.7 Returning Values with return

So far our functions have printed things. But often you want a function to calculate a result and send it back so your program can use it elsewhere.

The return keyword sends a value back to wherever the function was called:

python
def add(a, b):
    result = a + b
    return result

total = add(10, 25)
print("The total is:", total)

Expected output:

code
The total is: 35

What happens line by line:

  1. add(10, 25) is called ยท a = 10, b = 25
  2. Inside: result = 10 + 25 โ†’ result = 35
  3. return result sends 35 back to the caller
  4. total receives the returned value 35
  5. print(...) displays it

๐Ÿ›‘ After return, the function stops. Any code after return in the same function body will never run.

python
def demo():
    return "I am returned"
    print("This line never runs!")  # unreachable code

value = demo()
print(value)

Expected output:

code
I am returned

2.8 Practical Example ยท Temperature Converter

python
def celsius_to_fahrenheit(celsius):
    fahrenheit = (celsius * 9 / 5) + 32
    return fahrenheit

temp_c = 100
temp_f = celsius_to_fahrenheit(temp_c)
print(str(temp_c) + "ยฐC = " + str(temp_f) + "ยฐF")

print(str(0) + "ยฐC = " + str(celsius_to_fahrenheit(0)) + "ยฐF")
print(str(37) + "ยฐC = " + str(celsius_to_fahrenheit(37)) + "ยฐF")

Expected output:

code
100ยฐC = 212.0ยฐF
0ยฐC = 32.0ยฐF
37ยฐC = 98.6ยฐF

๐Ÿ’ก Real-world use: Weather apps, scientific instruments, and cooking sites use this exact type of function. Write it once, use it everywhere.


2.9 Passing a List as an Argument

Any data type can be passed as an argument, including lists:

python
def show_fruits(fruits):
    for fruit in fruits:
        print("๐ŸŽ " + fruit)

my_fruits = ["Apple", "Banana", "Mango", "Grape"]
show_fruits(my_fruits)

Expected output:

code
๐ŸŽ Apple
๐ŸŽ Banana
๐ŸŽ Mango
๐ŸŽ Grape

โœ๏ธ Your Task
Practise what you just learned about Arguments and Parameters. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 5 of 8
Part 3 ยท \*args and \*\*kwargs

3.1 The Problem: What If You Don't Know How Many Arguments There Will Be?

Imagine writing a function that adds numbers. How many numbers should it accept? Two? Five? Twenty?

If you hard-code three parameters, a caller who wants to sum six numbers is stuck. Python solves this with arbitrary arguments.


3.2 \*args ยท Arbitrary Positional Arguments

Adding a single * before a parameter name tells Python: "collect all the extra positional arguments into a tuple."

By convention, the name used is args, but any name after works.

python
def add_all(*numbers):
    total = 0
    for n in numbers:
        total = total + n
    return total

print(add_all(1, 2))
print(add_all(1, 2, 3, 4, 5))
print(add_all(10, 20, 30, 40, 50, 60))

Expected output:

code
3
15
210

What is numbers here? It is a tuple ยท an immutable, ordered collection of all the values passed in:

python
def inspect_args(*items):
    print(type(items))   # <class 'tuple'>
    print(items)

inspect_args("cat", "dog", "fish")

Expected output:

code
<class 'tuple'>
('cat', 'dog', 'fish')

3.3 \*args With Other Parameters

args can be combined with regular parameters. Regular parameters must come before args:

python
def show_menu(restaurant_name, *dishes):
    print("Restaurant: " + restaurant_name)
    print("Today's menu:")
    for dish in dishes:
        print("  - " + dish)

show_menu("La Piazza", "Pasta", "Pizza", "Tiramisu", "Risotto")

Expected output:

code
Restaurant: La Piazza
Today's menu:
  - Pasta
  - Pizza
  - Tiramisu
  - Risotto

๐Ÿง  How it works: "La Piazza" fills the restaurant_name parameter. Everything else ยท "Pasta", "Pizza", "Tiramisu", "Risotto" ยท is collected into the dishes tuple.


3.4 \\kwargs ยท Arbitrary Keyword Arguments

kwargs does the same thing as *args, but for keyword arguments (name=value pairs). The double collects them into a dictionary.

By convention the name is kwargs (keyword arguments), but you can use any name after .

python
def describe_person(**details):
    print("Person Profile:")
    for key, value in details.items():
        print("  " + key + ": " + str(value))

describe_person(name="Sarah", age=28, city="London", job="Engineer")

Expected output:

code
Person Profile:
  name: Sarah
  age: 28
  city: London
  job: Engineer

What is details here? It is a dictionary:

python
def inspect_kwargs(**info):
    print(type(info))   # <class 'dict'>
    print(info)

inspect_kwargs(colour="blue", size=12)

Expected output:

code
<class 'dict'>
{'colour': 'blue', 'size': 12}

3.5 \\kwargs ยท Practical Example: Product Builder

python
def create_product(**specs):
    print("=== New Product ===")
    for spec, value in specs.items():
        print(spec.capitalize() + ": " + str(value))
    print()

create_product(name="Laptop", brand="TechPro", ram="16GB", price=999.99)
create_product(name="Headphones", brand="SoundWave", colour="Black", wireless=True)

Expected output:

code
=== New Product ===
Name: Laptop
Brand: TechPro
Ram: 16GB
Price: 999.99

=== New Product ===
Name: Headphones
Brand: SoundWave
Colour: Black
Wireless: True

๐Ÿ’ก Real-world use: Web frameworks like Flask and Django use **kwargs everywhere. When you create a database model or an HTML element with attributes, the framework accepts them as keyword arguments.


3.6 Combining Regular, \args, and \\*kwargs

You can use all three together in one function. The order must be:

code
def function(regular_params, *args, **kwargs):
python
def full_order(table_number, *items, **extras):
    print("Table " + str(table_number) + " ordered:")
    for item in items:
        print("  - " + item)
    if extras:
        print("  Special requests:")
        for key, val in extras.items():
            print("    " + key + ": " + str(val))

full_order(7, "Pizza", "Salad", "Water", sauce="extra", gluten_free=True)

Expected output:

code
Table 7 ordered:
  - Pizza
  - Salad
  - Water
  Special requests:
    sauce: extra
    gluten_free: True

3.7 The \ and \\* Unpacking Operators in Function Calls

The and can also be used when calling a function, to unpack* a list or dictionary into separate arguments:

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

numbers = [1, 2, 3]
print(add(*numbers))   # unpacks list into: add(1, 2, 3)

Expected output:

code
6
python
def greet(name, language):
    print("Hello " + name + " in " + language)

settings = {"name": "Aiko", "language": "Japanese"}
greet(**settings)   # unpacks dict into: greet(name="Aiko", language="Japanese")

Expected output:

code
Hello Aiko in Japanese

3.8 Common Beginner Mistakes with \args and \\*kwargs

Mistake 1 ยท Wrong order:

python
# โŒ WRONG โ€“ **kwargs before *args
def bad_func(**kwargs, *args):   # SyntaxError
    pass

# โœ… CORRECT โ€“ *args always before **kwargs
def good_func(*args, **kwargs):
    pass

Mistake 2 ยท Treating \*args as a list (it's a tuple):

python
def my_func(*numbers):
    numbers.append(99)   # AttributeError! Tuples don't have append

# โœ… CORRECT โ€“ convert to list if you need to modify it
def my_func(*numbers):
    nums_list = list(numbers)
    nums_list.append(99)
    print(nums_list)

my_func(1, 2, 3)

Expected output:

code
[1, 2, 3, 99]

โœ๏ธ Your Task
Practise what you just learned about \args and \\*kwargs. Open your editor, type the examples above by hand, modify them, and observe what changes.
Phase 6 of 8
Part 4 ยท Python Scope

4.1 What Is Scope?

Scope describes where in your code a variable is visible and accessible. Not every variable can be seen from every part of a program ยท and this is intentional.

Why does scope exist? Imagine a huge program with hundreds of functions. If every variable was visible everywhere, any function could accidentally overwrite another function's total or count. Scope creates walls between different parts of code, preventing accidental interference.

Analogy: Think of scope like rooms in a house. A lamp in your bedroom (local scope) only lights your bedroom. It does not light the kitchen. The main electricity supply (global scope) powers the entire house.

Python has four levels of scope, remembered with the acronym LEGB:

LetterNameWhere
LLocalInside the current function
EEnclosingInside an outer function (for nested functions)
GGlobalAt the top level of the file
BBuilt-inPython's built-in names (e.g., print, len, range)

We will focus on Local and Global ยท the two you will encounter most.


4.2 Local Scope

A variable created inside a function is local ยท it only exists while the function is running, and it is invisible outside.

python
def my_function():
    local_variable = "I only exist inside this function"
    print(local_variable)

my_function()
print(local_variable)   # NameError! local_variable doesn't exist here

Expected output:

code
I only exist inside this function
Traceback (most recent call last):
  ...
NameError: name 'local_variable' is not defined

๐Ÿง  Why? When my_function() finishes, Python destroys all local variables. They are temporary ยท like writing on a whiteboard that gets erased when the meeting ends.


4.3 Local Variables in Different Functions Don't Interfere

Each function has its own private workspace. Two functions can both use a variable called x without conflict:

python
def function_a():
    x = 100
    print("Inside function_a, x =", x)

def function_b():
    x = 999
    print("Inside function_b, x =", x)

function_a()
function_b()

Expected output:

code
Inside function_a, x = 100
Inside function_b, x = 999

Both functions have their own x. They never interfere with each other. This is the power of local scope.


4.4 Global Scope

A variable created outside all functions is global ยท it is visible to all code in the file:

python
greeting = "Good morning"   # global variable

def say_it():
    print(greeting)         # can READ the global variable

def shout_it():
    print(greeting.upper()) # can also READ it

say_it()
shout_it()

Expected output:

code
Good morning
GOOD MORNING

4.5 Can a Function Modify a Global Variable?

By default, reading a global variable inside a function is fine. But modifying it is blocked ยท Python creates a new local variable instead:

python
counter = 0   # global

def increment():
    counter = counter + 1   # โŒ UnboundLocalError!

increment()

Why the error? Python sees counter = on the left side of = inside the function, assumes you mean a new local variable, then tries to read the (not-yet-created local) counter on the right side ยท and fails.


4.6 The global Keyword

To tell Python "I want to modify the actual global variable, not create a local one," use the global keyword:

python
counter = 0   # global

def increment():
    global counter             # declare intent to use the global
    counter = counter + 1
    print("Counter:", counter)

increment()
increment()
increment()
print("Final counter:", counter)

Expected output:

code
Counter: 1
Counter: 2
Counter: 3
Final counter: 3

โš ๏ธ Use global sparingly. Overusing global is considered bad practice because it makes code hard to trace and debug. The professional approach is to pass values as arguments and return results instead.

Professional alternative to global:

python
# โŒ Using global (not ideal)
total = 0
def add_to_total(n):
    global total
    total += n

# โœ… Better approach โ€“ pass and return
def add_to_total(current_total, n):
    return current_total + n

total = 0
total = add_to_total(total, 5)
total = add_to_total(total, 10)
print(total)  # 15

4.7 Local Takes Priority Over Global

If a local variable and a global variable share the same name, the local one wins inside the function:

python
name = "Global Name"

def show_name():
    name = "Local Name"   # this is a NEW local variable
    print(name)

show_name()
print(name)   # global variable unchanged

Expected output:

code
Local Name
Global Name

๐Ÿง  Notice: Assigning inside the function did NOT change the global name. The global is completely untouched.


4.8 The nonlocal Keyword (Bonus: Enclosing Scope)

When you have a function inside another function (a nested function), the inner function can access the outer function's variables as enclosing scope. To modify them, use nonlocal:

python
def outer():
    message = "Hello from outer"

    def inner():
        nonlocal message
        message = "Modified by inner"
        print("Inner says:", message)

    inner()
    print("Outer says:", message)

outer()

Expected output:

code
Inner says: Modified by inner
Outer says: Modified by inner

This is an advanced pattern. You will encounter it when learning about closures and decorators ยท but nonlocal follows exactly the same logic as global, just one level in.


4.9 Built-in Scope

Python has many built-in names that are always available ยท like print, len, range, type, int, str, list. These live in the built-in scope, the outermost layer:

python
print(len("Hello"))   # 'len' and 'print' are built-in
print(type(42))       # 'type' is built-in

Expected output:

code
5
<class 'int'>

โš ๏ธ Never shadow built-in names! Don't create variables called list, print, input, id, etc. ยท this overwrites the built-in and causes confusing errors.

python
# โŒ WRONG โ€“ never do this
list = [1, 2, 3]       # You've now overwritten the built-in 'list'
print(list([1,2,3]))   # TypeError โ€“ list is now just a list object, not a function

# โœ… CORRECT โ€“ use a descriptive name
my_list = [1, 2, 3]

โœ๏ธ Your Task
Professional alternative to global: `python # โŒ Using global (not ideal) total = 0 def add_to_total(n): global total total += n # โœ… Better approach ยท pass and return def add_to_total(current_total, n): return current_total + n total = 0 total = add_to_total(total, 5) total = add_to_total(total, 10) print(total) # 15 ` ยท
Phase 7 of 8
Guided Practice Exercises
๐ŸŽฏ Your Challenge

Exercise 1 ยท Grading Function

Objective: Write a function that takes a student's score and returns a letter grade.

Scenario: A teacher needs a quick tool to convert any numeric score to a grade.

Steps:

  1. Define a function called get_grade that accepts one parameter: score
  2. Inside, use if / elif / else to determine the grade:

- 90 ยท 100 โ†’ "A" - 80 ยท 89 โ†’ "B" - 70 ยท 79 โ†’ "C" - 60 ยท 69 โ†’ "D" - Below 60 โ†’ "F"

  1. Return the grade letter
  2. Call the function with at least five different scores and print each result

Hint: Use return to send the grade back, then print() outside the function.

Solution:

โœ๏ธ Task
Practise what you just learned about Guided Practice Exercises. Open your editor, type the examples above by hand, modify them, and observe what changes.
python
def get_grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    else:
        return "F"

scores = [95, 83, 72, 61, 45, 100, 59]
for s in scores:
    print("Score:", s, "โ†’ Grade:", get_grade(s))

Expected output:

code
Score: 95 โ†’ Grade: A
Score: 83 โ†’ Grade: B
Score: 72 โ†’ Grade: C
Score: 61 โ†’ Grade: D
Score: 45 โ†’ Grade: F
Score: 100 โ†’ Grade: A
Score: 59 โ†’ Grade: F

Self-check questions:

  • What grade does get_grade(60) return? Why?
  • What happens if you pass get_grade(105)? What should you do to handle that?

Exercise 2 ยท Shopping Bill with \*args

Objective: Use *args to calculate a total from any number of prices.

Scenario: A checkout system needs to add up a variable number of item prices.

python
def calculate_bill(customer_name, *prices):
    total = sum(prices)
    print("Customer: " + customer_name)
    print("Items: " + str(len(prices)))
    print("Total: ยฃ" + str(round(total, 2)))
    print()

calculate_bill("Alice", 4.99, 1.49, 7.25)
calculate_bill("Bob", 12.99, 3.49, 8.00, 2.50, 1.99)
calculate_bill("Carol", 35.00)

Expected output:

code
Customer: Alice
Items: 3
Total: ยฃ13.73

Customer: Bob
Items: 5
Total: ยฃ28.97

Customer: Carol
Items: 1
Total: ยฃ35.0

What-if challenge: What happens if you call calculate_bill("Dave") with no prices? Try it. Is the result sensible? How would you handle an empty bill?


Exercise 3 ยท User Profile with \\kwargs

Objective: Use **kwargs to build flexible user profiles.

python
def create_profile(username, **attributes):
    print("=== Profile: " + username + " ===")
    for key, value in attributes.items():
        print("  " + key.replace("_", " ").title() + ": " + str(value))
    print()

create_profile("coder99", age=22, city="Berlin", language="Python", years_experience=3)
create_profile("data_queen", age=30, field="Data Science", tools=["pandas", "numpy", "sklearn"])

Expected output:

code
=== Profile: coder99 ===
  Age: 22
  City: Berlin
  Language: Python
  Years Experience: 3

=== Profile: data_queen ===
  Age: 30
  Field: Data Science
  Tools: ['pandas', 'numpy', 'sklearn']

Exercise 4 ยท Scope Experiment

Objective: Understand what happens when local and global variables share a name.

python
x = "I am global"

def test_local():
    x = "I am local"
    print("Inside function:", x)

test_local()
print("Outside function:", x)

Expected output:

code
Inside function: I am local
Outside function: I am global

Questions:

  • Did the function change the global x? Why not?
  • What would you add to the function to actually change the global x?

Phase 8 of 8
Common Beginner Mistakes ยท Full Summary
MistakeExampleFix
Defining but not calling a functiondef greet(): print("Hi") with no callAdd greet() after the definition
Wrong indentationdef f(): then print(...) at column 0Indent the body with 4 spaces
Passing wrong number of argumentsdef add(a, b): ... called as add(1)Match the number of arguments to parameters
Confusing print with returnFunction prints but you try to store the resultUse return for values you need to use later
Wrong order: \\kwargs before \*argsdef f(*kw, ar)Must be def f(ar, *kw)
Modifying a global without global keywordcount = 0 โ†’ def f(): count += 1Add global count inside the function
Shadowing built-in nameslist = [1,2,3]Use descriptive names like my_list
Treating \*args as a listargs.append(x)args is a tuple; convert with list(args) first
Default parameter before non-defaultdef f(x=1, y)Non-defaults must come first: def f(y, x=1)

โœ๏ธ Your Task
Practise what you just learned about Common Beginner Mistakes ยท Full Summary. Open your editor, type the examples above by hand, modify them, and observe what changes.
๐Ÿ—๏ธ Build It โ€” Mini Project
Project Goal

This project combines functions, arguments, args, *kwargs, and scope into one realistic application.


Project Goal

Build a Student Report Generator that:

  1. Accepts student data (name, scores for multiple subjects, personal notes)
  2. Calculates the average score
  3. Assigns a letter grade
  4. Prints a formatted report

Stage 1 ยท Helper Functions (Setup)

starter.py
def calculate_average(*scores):
    """Calculate the average of any number of scores."""
    if len(scores) == 0:
        return 0
    return round(sum(scores) / len(scores), 1)

def get_grade(average):
    """Convert an average score to a letter grade."""
    if average >= 90:
        return "A โ€“ Excellent"
    elif average >= 80:
        return "B โ€“ Good"
    elif average >= 70:
        return "C โ€“ Satisfactory"
    elif average >= 60:
        return "D โ€“ Needs Improvement"
    else:
        return "F โ€“ Unsatisfactory"

# Test Stage 1
avg = calculate_average(85, 90, 78, 92)
print("Average:", avg)
print("Grade:", get_grade(avg))

Lesson 15 complete! ๐ŸŽ‰

You covered: