📘 Day 13: Comparison and logical operators (and, or, not)
Comparison operators let you ask how two values relate to each other.
When Python evaluates an expression like
The six basic comparison symbols are:
Why does
Logical operators combine those Boolean results.
Python evaluates the left side first; if it is
It also short‑circuits: if the left side is
*
The precedence order is
Putting it together – a realistic example
Walk‑through
1.
Python compares the two integers and produces a Boolean.
2.
First part:
Second part:
3. The outer
Both sides are true, so the combined condition is true.
4.
If you changed
Common pitfalls
* Using
* Chaining comparisons without parentheses – Python allows
* Relying on truthy/falsy values – non‑Boolean objects (e.g., empty list
Understanding why Python returns Booleans and how short‑circuit evaluation works lets you write more efficient and readable conditions. Practice combining them, and you’ll soon master decision‑making in code.
Comparison operators let you ask how two values relate to each other.
When Python evaluates an expression like
a == b it does not return the numbers, it returns a Boolean value – either True or False. The six basic comparison symbols are:
== – equal != – not equal > – greater than < – less than >= – greater‑or‑equal <= – less‑or‑equal Why does
5 > 3 become True? Python compares the binary representation of the numbers internally and decides which one has the larger magnitude. The result of the comparison is stored as a bool object, a subclass of int (True is 1, False is 0). This lets you use the result directly in control flow (if, while) or as part of larger logical statements.Logical operators combine those Boolean results.
and – returns True only if* both operands are True. Python evaluates the left side first; if it is
False it short‑circuits and skips the right side because the whole expression can never become True.or – returns True if at least one* operand is True. It also short‑circuits: if the left side is
True, Python never evaluates the right side.*
not – a unary operator that flips a Boolean value: not True → False.The precedence order is
not > and > or. Parentheses let you override it, just like in arithmetic.Putting it together – a realistic example
# Determine if a user can access premium content
age = 22
has_subscription = False
is_employee = True
# Must be at least 18 AND (have a subscription OR be an employee)
can_access = (age >= 18) and (has_subscription or is_employee)
print("Access granted?" , can_access)
Walk‑through
1.
age >= 18 → 22 >= 18 → True Python compares the two integers and produces a Boolean.
2.
has_subscription or is_employee → First part:
has_subscription is False. Because it is False, or evaluates the right side. Second part:
is_employee is True → the whole or expression becomes True.3. The outer
and now sees True and True → True. Both sides are true, so the combined condition is true.
4.
can_access stores that Boolean, and the print statement shows Access granted? True.If you changed
age to 16, the first comparison would be False. Because and short‑circuits, Python would skip the whole right side and can_access would be False immediately.Common pitfalls
* Using
= instead of == – = assigns, == compares. * Chaining comparisons without parentheses – Python allows
a < b < c, which means a < b and b < c. * Relying on truthy/falsy values – non‑Boolean objects (e.g., empty list
[] or number 0) are treated as False in logical contexts, which can be handy but sometimes confusing.Understanding why Python returns Booleans and how short‑circuit evaluation works lets you write more efficient and readable conditions. Practice combining them, and you’ll soon master decision‑making in code.
Which of the following statements about the `and` operator is correct?
Anonymous Quiz
0%
It evaluates both operands always
0%
It returns the first falsy operand
0%
It returns the second operand if the first is True
0%
It short‑circuits only when both are True
📘 Day 14: for loops
For loops are one of the most fundamental control structures in Python. They let you repeat a block of code for each item in a collection, called an iterable. Understanding why they work the way they do will make you write clearer, more efficient code.
1️⃣ What is an iterable?
An iterable is any object that can return its items one at a time, producing an iterator when you call
list, tuple, string, range, dict, set, file objects.
When a
1. Calls
2. Calls
3. Executes the loop body with that item, then repeats step 2 until
Because this logic is hidden, you only write
2️⃣ Basic syntax
-
- The block is indented (usually 4 spaces) and runs for every element.
3️⃣ The simplest example – counting numbers
What happens:
1.
2. The loop assigns
3. It repeats until the last value (
Why
4️⃣ Looping over collections
- The list
- Each iteration binds
- You can modify the element or use it in any expression.
5️⃣ Getting the index with
Often you need both the position and the value:
-
-
6️⃣ Parallel iteration with
When you have two (or more) iterables of the same length:
-
- The loop stops at the shortest iterable, preventing IndexError.
7️⃣ Controlling the loop:
-
-
- An
Why the
8️⃣ A complete, runnable example
(continued below 👇)
For loops are one of the most fundamental control structures in Python. They let you repeat a block of code for each item in a collection, called an iterable. Understanding why they work the way they do will make you write clearer, more efficient code.
1️⃣ What is an iterable?
An iterable is any object that can return its items one at a time, producing an iterator when you call
iter() on it. Common iterables:list, tuple, string, range, dict, set, file objects.
When a
for loop starts, Python internally does three steps:1. Calls
iter() on the given object → gets an iterator.2. Calls
next() on that iterator → fetches the first item.3. Executes the loop body with that item, then repeats step 2 until
StopIteration is raised.Because this logic is hidden, you only write
for item in iterable: and Python handles the rest.2️⃣ Basic syntax
for <variable> in <iterable>:
<block of code>
-
<variable> receives each element one by one.- The block is indented (usually 4 spaces) and runs for every element.
3️⃣ The simplest example – counting numbers
for i in range(5):
print(i)
What happens:
1.
range(5) creates an iterable that yields 0, 1, 2, 3, 4.2. The loop assigns
i = 0 then runs print(i).3. It repeats until the last value (
4) is printed, then exits.Why
range? It’s a lazy iterable – it doesn’t build a full list in memory; it generates numbers on the fly, which is efficient for large sequences.4️⃣ Looping over collections
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
print('I like', fruit)
- The list
fruits is an iterable.- Each iteration binds
fruit to the next element.- You can modify the element or use it in any expression.
5️⃣ Getting the index with
enumerateOften you need both the position and the value:
for idx, fruit in enumerate(fruits, start=1):
print(f'{idx}. {fruit}')
-
enumerate wraps any iterable and yields pairs (index, value).-
start=1 makes indexing human‑friendly (1‑based).6️⃣ Parallel iteration with
zipWhen you have two (or more) iterables of the same length:
colors = ['red', 'yellow', 'red']
for fruit, color in zip(fruits, colors):
print(f'{fruit} is {color}')
-
zip produces tuples (fruit, color) for each position.- The loop stops at the shortest iterable, preventing IndexError.
7️⃣ Controlling the loop:
break, continue, and else-
break exits the loop immediately.-
continue skips the rest of the current iteration and goes to the next.- An
else block runs only if the loop completed normally (no break).total = 0
for i in range(1, 4): # 1, 2, 3
total += i
if i == 2:
continue # skip the rest for i==2
else:
total *= 2 # runs because no break occurred
print(total) # -> 12
Why the
else? It lets you differentiate between “finished all items” and “stopped early with a break”.8️⃣ A complete, runnable example
(continued below 👇)
# -*- coding: utf-8 -*-
# Demonstrates various for‑loop features
fruits = ['apple', 'banana', 'cherry']
prices = [0.99, 0.59, 1.29]
print('--- List each fruit with its price ---')
for fruit, price in zip(fruits, prices):
print(f'{fruit.title():<6} costs ${price:.2f}')
print('\n--- Show index and fruit, stop at banana ---')
for idx, fruit in enumerate(fruits):
if fruit == 'banana':
print('Found banana, stopping loop.')
break
print(f'{idx}: {fruit}')
print('\n--- Sum of prices, with else clause ---')
total = 0
for p in prices:
total += p
else: # runs because we never break
print(f'Total price is ${total:.2f}')
Walk‑through:
1.
zip(fruits, prices) creates pairs: ('apple',0.99), ('banana',0.59), ('cherry',1.29). The loop prints each nicely formatted line.
2.
enumerate(fruits) yields (0,'apple'), (1,'banana'), (2,'cherry'). When
'banana' is found, break stops the loop; the else after the loop (not shown here) would be skipped.3. The final loop adds each price to
total. Because we never break, the else block executes, printing the sum.Run this script – it will print a clear demonstration of each concept.
9️⃣ Common pitfalls
- Modifying the iterable while looping can lead to unexpected behavior. Iterate over a copy (
for x in mylist[:]) if you need to remove items.- Using
range with mutable defaults: for i in range(len(list)): is fine, but prefer direct iteration (for item in list:) for readability.- Forgot the colon after the
for statement – Python will raise a SyntaxError.🔑 Bottom line
A
for loop abstracts the iterator protocol, letting you focus on what to do with each element rather than how to fetch it. Mastering range, enumerate, zip, and loop‑control statements turns a simple repetition into a powerful tool for data processing, algorithmic logic, and clean code.What value does the variable `total` have after this code runs?
Anonymous Quiz
0%
12
50%
10
50%
6
0%
8
📘 Day 15: while loops
A while loop lets you repeat a block of code as long as a condition stays true. It’s the most direct way to create a loop that depends on a condition that may change inside the loop body.
Why it works – Python evaluates the condition expression before each iteration.
- If the condition is truthy (anything that evaluates to True), the body runs.
- After the body finishes, Python goes back to re‑evaluate the condition.
- When the condition becomes falsy (False, 0, empty collection, None, etc.) the loop stops and execution continues after the loop.
This “check‑first” behaviour is different from a for‑loop, which iterates over a known sequence. With a while‑loop you often don’t know in advance how many times you’ll need to repeat – the loop stops exactly when the condition flips.
Anatomy of a while loop
-
-
-
-
If the body never changes the condition, you get an infinite loop – the program will never exit the loop on its own. To avoid that, you usually:
- Update a variable inside the body, or
- Use
- Use
A complete, runnable example
Walk‑through
1.
2.
3.
4.
5.
6.
Running this script prints:
Common patterns & pitfalls
- Sentinel loop – keep reading input until a special value (the sentinel) appears.
- Infinite loop with
-
- Truthiness matters – any non‑zero number, non‑empty container, or non‑False object counts as true.
(continued below 👇)
A while loop lets you repeat a block of code as long as a condition stays true. It’s the most direct way to create a loop that depends on a condition that may change inside the loop body.
Why it works – Python evaluates the condition expression before each iteration.
- If the condition is truthy (anything that evaluates to True), the body runs.
- After the body finishes, Python goes back to re‑evaluate the condition.
- When the condition becomes falsy (False, 0, empty collection, None, etc.) the loop stops and execution continues after the loop.
This “check‑first” behaviour is different from a for‑loop, which iterates over a known sequence. With a while‑loop you often don’t know in advance how many times you’ll need to repeat – the loop stops exactly when the condition flips.
Anatomy of a while loop
while <condition>:
<body>
-
while – keyword that starts the loop.-
<condition> – any expression that returns a Boolean value.-
: – colon marks the start of the indented block.-
<body> – one or more indented statements that run each time the condition is true.If the body never changes the condition, you get an infinite loop – the program will never exit the loop on its own. To avoid that, you usually:
- Update a variable inside the body, or
- Use
break to exit early, or- Use
continue to skip the rest of the current iteration and re‑check the condition.A complete, runnable example
# Countdown from 5 to 0 using a while loop
counter = 5 # start value
while counter >= 0: # condition: keep looping while counter is 0 or more
print(f"Counter is {counter}")
counter -= 1 # decrease counter by 1 each iteration
else:
print("Loop finished!") # this runs once after the condition becomes false
Walk‑through
1.
counter = 5 – Initializes a variable that the loop will monitor.2.
while counter >= 0: – The loop will continue as long as counter is greater than or equal to 0.3.
print(f"Counter is {counter}") – Shows the current value each time the loop runs.4.
counter -= 1 – Decrements counter. This is the crucial step that eventually makes the condition false; without it, the loop would never stop.5.
else: – The else‑clause of a while loop executes once, after the condition becomes false unless the loop is exited with break. It’s handy for cleanup or final messages.6.
print("Loop finished!") – Runs after the loop ends, confirming successful termination.Running this script prints:
Counter is 5
Counter is 4
Counter is 3
Counter is 2
Counter is 1
Counter is 0
Loop finished!
Common patterns & pitfalls
- Sentinel loop – keep reading input until a special value (the sentinel) appears.
word = ""
while word != "quit":
word = input("Type a word (or 'quit' to stop): ")
print("You typed:", word)
- Infinite loop with
break – useful when the exit condition is discovered inside the body.while True:
data = fetch_next()
if data is None: # no more data
break
process(data)
-
continue – skips to the next iteration, re‑evaluating the condition immediately.i = 0
while i < 10:
i += 1
if i % 2 == 0: # skip even numbers
continue
print(i) # prints only odd numbers
- Truthiness matters – any non‑zero number, non‑empty container, or non‑False object counts as true.
items = [1, 2, 3]
while items: # loops while the list is not empty
print(items.pop()) # removes the last item each iteration
(continued below 👇)
- Avoiding off‑by‑one errors – ensure the condition and the update step move towards termination.
When to prefer a while loop
- Indeterminate repetitions – you don’t know ahead of time how many times you need to loop (e.g., waiting for user input, reading a file until EOF).
- Complex exit logic – the stop condition may depend on multiple variables updated inside the loop.
- Resource‑waiting loops – repeatedly check a condition (like a network response) until it becomes true.
Quick checklist before you run a while loop
- Is the condition initially true? If not, the loop body never runs.
- Will something inside the body eventually make the condition false? If not, you’ll have an infinite loop.
- Do you need an early exit? Use
- Do you need to skip the rest of the current iteration? Use
- Do you want a final action after normal termination? Add an
Mastering while loops gives you fine‑grained control over program flow, especially in situations where the number of iterations is driven by dynamic data rather than a fixed collection. Practice by rewriting simple
When to prefer a while loop
- Indeterminate repetitions – you don’t know ahead of time how many times you need to loop (e.g., waiting for user input, reading a file until EOF).
- Complex exit logic – the stop condition may depend on multiple variables updated inside the loop.
- Resource‑waiting loops – repeatedly check a condition (like a network response) until it becomes true.
Quick checklist before you run a while loop
- Is the condition initially true? If not, the loop body never runs.
- Will something inside the body eventually make the condition false? If not, you’ll have an infinite loop.
- Do you need an early exit? Use
break.- Do you need to skip the rest of the current iteration? Use
continue.- Do you want a final action after normal termination? Add an
else clause.Mastering while loops gives you fine‑grained control over program flow, especially in situations where the number of iterations is driven by dynamic data rather than a fixed collection. Practice by rewriting simple
for loops as while loops and observe how the condition and updates interact. Happy looping!What must happen inside a while loop to guarantee it eventually stops?
Anonymous Quiz
0%
The condition is never checked
0%
The loop variable is never changed
0%
The loop body includes a break statement
0%
Something inside the loop changes the condition to become false
📘 Day 16: break, continue, and the loop else clause
When you write a loop in Python you usually expect it to run from the first element to the last, doing the same block of code over and over.
Sometimes you need more control: stop early, skip the rest of the current iteration, or run a piece of code only if the loop wasn’t stopped early.
That’s what
When Python reaches a
All remaining iterations are ignored, and execution continues with the first statement after the loop.
Why it works – A
When Python sees a
Why it works – Internally Python raises a different hidden exception that tells the loop “start the next cycle now”. The loop catches it, skips the rest of the body, and proceeds.
A loop can have an
The
Why it works – After each iteration the loop checks two things:
1. Did we exit because of
2. Did we run out of items / condition became false? → run
Putting it all together – a complete example
Walk‑through
1.
2.
3.
4. For any other positive number, we reach the regular
5. If the loop runs through the whole list without ever hitting
6. Finally,
What you’ll see when you run it
Notice that the
When to use each construct
(continued below 👇)
When you write a loop in Python you usually expect it to run from the first element to the last, doing the same block of code over and over.
Sometimes you need more control: stop early, skip the rest of the current iteration, or run a piece of code only if the loop wasn’t stopped early.
That’s what
break, continue, and the loop‑else clause give you.break – leaving the loop right now When Python reaches a
break, it immediately exits the innermost loop (whether for or while). All remaining iterations are ignored, and execution continues with the first statement after the loop.
Why it works – A
break raises a hidden internal exception that is caught by the loop machinery. The loop catches that exception, cleans up, and jumps out.continue – skip to the next iteration When Python sees a
continue, it stops the current iteration right there and jumps to the top of the loop to evaluate the next item (or the loop condition again for a while). The code that follows continue inside the loop body is never run for that iteration.Why it works – Internally Python raises a different hidden exception that tells the loop “start the next cycle now”. The loop catches it, skips the rest of the body, and proceeds.
else on a loop – run only if the loop finished normally A loop can have an
else block:for item in iterable:
# loop body
else:
# runs when the loop **did NOT** encounter a break
The
else block is executed after the loop finishes its natural course (i.e., the iterator is exhausted or the while condition became false). If a break occurs, the else block is skipped.Why it works – After each iteration the loop checks two things:
1. Did we exit because of
break? → skip else. 2. Did we run out of items / condition became false? → run
else.Putting it all together – a complete example
numbers = [5, 12, 7, -3, 0, 9, 4]
print("Scanning numbers...")
for n in numbers:
# 1️⃣ Skip negative numbers – we don’t want to process them
if n < 0:
print(f" {n} is negative → continue")
continue # jump to the next item
# 2️⃣ Stop the whole scan as soon as we hit zero
if n == 0:
print(" Zero found! → break")
break # leave the loop completely
# Normal processing for positive, non‑zero numbers
print(f" {n} is ok, processing...")
else:
# This runs only if we never hit the `break`
print("All numbers processed without hitting zero.")
print("Done.")
Walk‑through
1.
for n in numbers: – starts iterating over the list. 2.
if n < 0: – detects a negative value. continue prints a message and jumps to the next element, so the rest of the body (print of “ok”) is skipped for that iteration. 3.
if n == 0: – when a zero appears, we print a message and break. The loop ends immediately, so the else block is not executed. 4. For any other positive number, we reach the regular
print and continue looping. 5. If the loop runs through the whole list without ever hitting
break, the else: block runs, confirming a “clean” finish. 6. Finally,
print("Done.") runs after the loop (whether we broke out early or not).What you’ll see when you run it
Scanning numbers...
5 is ok, processing...
12 is ok, processing...
7 is ok, processing...
-3 is negative → continue
Zero found! → break
Done.
Notice that the
else message never appears because we hit break at the zero.When to use each construct
(continued below 👇)
-
-
-
Quick comparison
-
-
-
Mastering these three tools lets you write loops that are both expressive and efficient, avoiding extra flags or nested conditionals.
break – when a condition means “the rest of the work is pointless”. Typical examples: searching for an item, validating input, early exit on error.-
continue – when you want to ignore certain items but keep scanning the rest. Useful for filtering inside a loop without extra nesting.-
else on loops – when you need a “no‑break” confirmation. Common in search patterns: “found it → break; otherwise → after‑loop else means not found”.Quick comparison
-
break → stop the loop now. -
continue → skip the rest of this iteration, go to next. -
else → run after the loop only if no break happened.Mastering these three tools lets you write loops that are both expressive and efficient, avoiding extra flags or nested conditionals.
What happens to a loop's `else` block if a `break` statement is executed inside the loop?
Anonymous Quiz
50%
It still runs after the loop
0%
It runs only once before the break
0%
It is skipped entirely
50%
It runs only if the loop is a `while`
📘 Day 17: Writing your first function (def, parameters, return)
def is the keyword that tells Python you are creating a function – a reusable block of code that can take parameters (inputs), do something, and optionally return a result.
Why use functions?
- Modularity – break a large program into smaller, manageable pieces.
- Reusability – call the same logic many times without rewriting it.
- Readability – give a meaningful name to a task, so the code reads like plain English.
When you write a function, three things happen behind the scenes:
1. Definition time – Python stores the function object under the name you give it. No code inside runs yet.
2. Call time – you use the name followed by parentheses, optionally passing arguments that match the parameters. Python creates a new local scope for that call.
3. Return – the return statement sends a value back to the caller and ends the function immediately. If you omit return, Python automatically returns None.
Step‑by‑step example
What each part does
-
- The triple‑quoted string right after the header is a docstring. It describes what the function does and can be accessed via
-
-
-
-
-
Key points to remember
- Parameters vs. arguments – Parameters are the names in the function definition; arguments are the actual values you pass when calling.
- Scope – Variables created inside a function are local and disappear after the function returns. They do not affect variables with the same name outside.
- Return value – Only one
- No return – If a function reaches the end without a
A slightly more complex function
(continued below 👇)
def is the keyword that tells Python you are creating a function – a reusable block of code that can take parameters (inputs), do something, and optionally return a result.
Why use functions?
- Modularity – break a large program into smaller, manageable pieces.
- Reusability – call the same logic many times without rewriting it.
- Readability – give a meaningful name to a task, so the code reads like plain English.
When you write a function, three things happen behind the scenes:
1. Definition time – Python stores the function object under the name you give it. No code inside runs yet.
2. Call time – you use the name followed by parentheses, optionally passing arguments that match the parameters. Python creates a new local scope for that call.
3. Return – the return statement sends a value back to the caller and ends the function immediately. If you omit return, Python automatically returns None.
Step‑by‑step example
def greet(name):
"""
Build a friendly greeting.
The parameter `name` receives the caller's argument.
"""
# Inside the function we create a local variable `message`.
message = f"Hello, {name}!" # f‑string inserts the value of `name`.
return message # Send `message` back to the caller.
# ---- calling the function ----
user_input = "Alice" # This could come from anywhere.
result = greet(user_input) # `user_input` is passed as an argument.
print(result) # Prints: Hello, Alice!
What each part does
-
def greet(name): – Starts the definition. greet is the function name; name is a parameter that will receive a value when we call it. - The triple‑quoted string right after the header is a docstring. It describes what the function does and can be accessed via
greet.__doc__. -
message = f"Hello, {name}!" – A local variable; it exists only while this call runs. The f‑string formats the greeting by inserting the current value of name. -
return message – Sends the constructed string back to wherever the function was called. Execution of the function stops here. -
user_input = "Alice" – An ordinary variable holding the data we want to greet. -
result = greet(user_input) – Calling the function. user_input is an argument that gets bound to the parameter name. The function runs, returns the greeting, and that value is stored in result. -
print(result) – Uses the returned value just like any other variable. Key points to remember
- Parameters vs. arguments – Parameters are the names in the function definition; arguments are the actual values you pass when calling.
- Scope – Variables created inside a function are local and disappear after the function returns. They do not affect variables with the same name outside.
- Return value – Only one
return is executed per call. If you need to exit early, you can place return statements in conditional blocks. - No return – If a function reaches the end without a
return, Python automatically returns None, which is a special “nothing” object. A slightly more complex function
def calculate_stats(numbers):
"""Return the sum and average of a list of numbers."""
total = sum(numbers) # built‑in sum adds all items
average = total / len(numbers) # len gives the count
return total, average # returns a tuple (total, average)
data = [4, 8, 15, 16, 23, 42]
sum_val, avg_val = calculate_stats(data)
print(f"Sum: {sum_val}, Average: {avg_val:.2f}")
(continued below 👇)
- Here
- The
By mastering def, parameters, and return, you now have the core tools to build any reusable piece of logic in Python. Practice by writing small functions that perform arithmetic, string manipulation, or data processing, and you’ll quickly see how they make your code cleaner and more powerful.
calculate_stats takes one parameter numbers, which we expect to be an iterable of numeric values. - The
return total, average line actually returns a tuple containing both results. When we call the function, we can unpack that tuple directly into sum_val and avg_val. By mastering def, parameters, and return, you now have the core tools to build any reusable piece of logic in Python. Practice by writing small functions that perform arithmetic, string manipulation, or data processing, and you’ll quickly see how they make your code cleaner and more powerful.
What does a Python function return if it reaches the end without a `return` statement?
Anonymous Quiz
0%
The last evaluated expression
0%
None
0%
0
0%
An empty string
📘 Day 18: Default arguments and keyword arguments
Default arguments let you give a function parameter a value that will be used if the caller does not supply one. Keyword arguments let the caller specify a value by naming the parameter, so the order of the arguments can change.
When Python creates a function object, it stores the default values in a tuple called
How the interpreter matches arguments
1. Positional arguments are matched to parameters in the order they appear.
2. Keyword arguments are matched by name, regardless of position.
3. After all positional arguments are assigned, any remaining parameters that have defaults get those defaults automatically.
4. If a parameter has no default and the caller didn’t provide a value, Python raises
Why keyword arguments are powerful
- You can skip over optional parameters you don’t want to change.
- The call becomes self‑documenting:
- They allow you to call a function with a subset of optional arguments without remembering the exact order.
Common pitfalls with mutable defaults
Calling this repeatedly:
What happened?
The list
Correct pattern – use
Now the calls are independent:
Full runnable example – mixing defaults and keywords
Walk‑through
-
- The function body builds a formatted string using the three parameters.
- In call 1, only
- In call 2,
- In call 3, every argument is supplied by name, so the order can be shuffled.
- In call 4,
Quick checklist (bolded list)
(continued below 👇)
Default arguments let you give a function parameter a value that will be used if the caller does not supply one. Keyword arguments let the caller specify a value by naming the parameter, so the order of the arguments can change.
When Python creates a function object, it stores the default values in a tuple called
__defaults__. This tuple is built once, at function definition time, not each time the function runs. Because of that, the same object is reused for every call that omits the argument. This is why mutable defaults (like a list or dict) can keep state between calls – the object lives as long as the function object lives.How the interpreter matches arguments
1. Positional arguments are matched to parameters in the order they appear.
2. Keyword arguments are matched by name, regardless of position.
3. After all positional arguments are assigned, any remaining parameters that have defaults get those defaults automatically.
4. If a parameter has no default and the caller didn’t provide a value, Python raises
TypeError.Why keyword arguments are powerful
- You can skip over optional parameters you don’t want to change.
- The call becomes self‑documenting:
process(data, mode='fast') tells the reader what 'fast' means.- They allow you to call a function with a subset of optional arguments without remembering the exact order.
Common pitfalls with mutable defaults
def append_to_log(message, log=[]):
"""Add a message to a growing log list."""
log.append(message)
return log
Calling this repeatedly:
print(append_to_log('first')) # ['first']
print(append_to_log('second')) # ['first', 'second']What happened?
The list
log is created once when append_to_log is defined. Each call re‑uses the same list, so it accumulates items across calls. This is often unintended.Correct pattern – use
None as the sentinel and create a new list inside the function:def append_to_log(message, log=None):
"""Add a message to a fresh log list each call."""
if log is None:
log = [] # new list for this call
log.append(message)
return log
Now the calls are independent:
print(append_to_log('first')) # ['first']
print(append_to_log('second')) # ['second']Full runnable example – mixing defaults and keywords
def greet(name, greeting='Hello', punctuation='!'):
"""
Return a personalised greeting.
* name – required, positional or keyword
* greeting – optional, defaults to 'Hello'
* punctuation – optional, defaults to '!'
"""
return f"{greeting}, {name}{punctuation}"
# 1️⃣ Positional only
print(greet('Alice')) # Hello, Alice!
# 2️⃣ Positional + one keyword (out of order)
print(greet('Bob', punctuation='.' )) # Hello, Bob.
# 3️⃣ All keywords – order doesn't matter
print(greet(name='Carol', punctuation='?', greeting='Hi')) # Hi, Carol?
# 4️⃣ Mixing positional and keyword
print(greet('Dave', greeting='Good morning')) # Good morning, Dave!
Walk‑through
-
def greet(name, greeting='Hello', punctuation='!'): defines two default arguments (greeting and punctuation). - The function body builds a formatted string using the three parameters.
- In call 1, only
name is supplied; the defaults fill in greeting and punctuation.- In call 2,
name and punctuation are given; greeting falls back to its default.- In call 3, every argument is supplied by name, so the order can be shuffled.
- In call 4,
name is positional, greeting is a keyword, and punctuation uses its default.Quick checklist (bolded list)
(continued below 👇)
- Always place parameters with defaults after those without.
- Use
- Keyword arguments can be mixed with positional but must come after all positional arguments in the call.
- Defaults are evaluated once at definition time, not at each call.
- Use
None as a sentinel for mutable default values. - Keyword arguments can be mixed with positional but must come after all positional arguments in the call.
- Defaults are evaluated once at definition time, not at each call.
Which statement about default argument values in Python is true?
Anonymous Quiz
100%
They are evaluated each time the function is called
0%
They are evaluated once when the function is defined
0%
They can only be immutable objects
0%
They must be literals
📘 **Day 19: *args and **kwargs**
When you define a Python function, you usually list every parameter it expects:
```python
def greet(name, age):
print(f"Hello {name}, you are {age} years old.")
```
But real‑world functions often need **flexibility** – they may receive an unknown number of values or a set of named options that can change from call to call. That’s where `*args` and `**kwargs` come in.
**What `*args` does**
- The asterisk (`*`) before a parameter name tells Python *“collect any extra *positional* arguments into a tuple”*.
- Inside the function, `args` behaves exactly like any other tuple: you can iterate, index, or unpack it.
**What `**kwargs` does**
- The double asterisk (`**`) before a parameter name tells Python *“collect any extra *keyword* arguments into a dictionary”*.
- Inside the function, `kwargs` works like any dict: you can query keys, iterate over items, or use `.get()`.
**Why it works**
When a function call is evaluated, Python separates arguments into three groups:
1. **Positional arguments** that match the function’s regular parameters.
2. **Extra positional arguments** that don’t have a matching parameter – they go into `*args`.
3. **Keyword arguments** that match named parameters, plus any additional named arguments – the extras go into `**kwargs`.
Python builds `args` as a **tuple** because the order of positional arguments matters and they are immutable. It builds `kwargs` as a **dict** because each keyword must be unique and you need fast lookup by name.
**Calling a function with `*` and `**`**
The same symbols can be used *in reverse* when you **unpack** a sequence or mapping into a function call:
```python
values = (1, 2, 3)
options = {"verbose": True, "mode": "fast"}
def demo(a, b, c, verbose=False, mode="normal"):
print(a, b, c, verbose, mode)
demo(*values, **options) # expands to demo(1, 2, 3, verbose=True, mode="fast")
```
**Ordering rules**
When you mix regular parameters, `*args`, and `**kwargs` in a definition, the order must be:
```
def func(pos1, pos2, *args, kw1=None, kw2=None, **kwargs):
...
```
- Positional‑only (if using `/`), then normal positional, then `*args`, then keyword‑only, then `**kwargs`.
---
**Complete runnable example**
```python
def summarize(*numbers, **options):
"""
Prints a summary of any numeric values passed in,
and shows optional settings provided as keywords.
"""
# 1️⃣ *numbers is a tuple of all extra positional arguments
total = sum(numbers)
count = len(numbers)
# 2️⃣ **options is a dict of all extra keyword arguments
prefix = options.get("prefix", "Result")
show_average = options.get("average", False)
print(f"{prefix}:")
print(f" Count = {count}")
print(f" Sum = {total}")
if show_average and count > 0:
print(f" Avg = {total / count}")
# Calling with various combos
summarize(4, 7, 1, 3, prefix="Stats", average=True)
summarize(10, 20) # no extra keywords
summarize() # no numbers at all
```
**Walk‑through**
1. `def summarize(*numbers, **options):` –
- `*numbers` gathers **any number** of positional arguments into a tuple called `numbers`.
- `**options` gathers **any number** of keyword arguments into a dict called `options`.
2. Inside the function:
- `total = sum(numbers)` uses the built‑in `sum` on the tuple – works because tuples are iterable.
- `count = len(numbers)` gives how many numbers were passed.
- `options.get("prefix", "Result")` fetches the value for key `"prefix"` if present; otherwise defaults to `"Result"`.
- `options.get("average", False)` decides whether to display the average.
(continued below 👇)
When you define a Python function, you usually list every parameter it expects:
```python
def greet(name, age):
print(f"Hello {name}, you are {age} years old.")
```
But real‑world functions often need **flexibility** – they may receive an unknown number of values or a set of named options that can change from call to call. That’s where `*args` and `**kwargs` come in.
**What `*args` does**
- The asterisk (`*`) before a parameter name tells Python *“collect any extra *positional* arguments into a tuple”*.
- Inside the function, `args` behaves exactly like any other tuple: you can iterate, index, or unpack it.
**What `**kwargs` does**
- The double asterisk (`**`) before a parameter name tells Python *“collect any extra *keyword* arguments into a dictionary”*.
- Inside the function, `kwargs` works like any dict: you can query keys, iterate over items, or use `.get()`.
**Why it works**
When a function call is evaluated, Python separates arguments into three groups:
1. **Positional arguments** that match the function’s regular parameters.
2. **Extra positional arguments** that don’t have a matching parameter – they go into `*args`.
3. **Keyword arguments** that match named parameters, plus any additional named arguments – the extras go into `**kwargs`.
Python builds `args` as a **tuple** because the order of positional arguments matters and they are immutable. It builds `kwargs` as a **dict** because each keyword must be unique and you need fast lookup by name.
**Calling a function with `*` and `**`**
The same symbols can be used *in reverse* when you **unpack** a sequence or mapping into a function call:
```python
values = (1, 2, 3)
options = {"verbose": True, "mode": "fast"}
def demo(a, b, c, verbose=False, mode="normal"):
print(a, b, c, verbose, mode)
demo(*values, **options) # expands to demo(1, 2, 3, verbose=True, mode="fast")
```
**Ordering rules**
When you mix regular parameters, `*args`, and `**kwargs` in a definition, the order must be:
```
def func(pos1, pos2, *args, kw1=None, kw2=None, **kwargs):
...
```
- Positional‑only (if using `/`), then normal positional, then `*args`, then keyword‑only, then `**kwargs`.
---
**Complete runnable example**
```python
def summarize(*numbers, **options):
"""
Prints a summary of any numeric values passed in,
and shows optional settings provided as keywords.
"""
# 1️⃣ *numbers is a tuple of all extra positional arguments
total = sum(numbers)
count = len(numbers)
# 2️⃣ **options is a dict of all extra keyword arguments
prefix = options.get("prefix", "Result")
show_average = options.get("average", False)
print(f"{prefix}:")
print(f" Count = {count}")
print(f" Sum = {total}")
if show_average and count > 0:
print(f" Avg = {total / count}")
# Calling with various combos
summarize(4, 7, 1, 3, prefix="Stats", average=True)
summarize(10, 20) # no extra keywords
summarize() # no numbers at all
```
**Walk‑through**
1. `def summarize(*numbers, **options):` –
- `*numbers` gathers **any number** of positional arguments into a tuple called `numbers`.
- `**options` gathers **any number** of keyword arguments into a dict called `options`.
2. Inside the function:
- `total = sum(numbers)` uses the built‑in `sum` on the tuple – works because tuples are iterable.
- `count = len(numbers)` gives how many numbers were passed.
- `options.get("prefix", "Result")` fetches the value for key `"prefix"` if present; otherwise defaults to `"Result"`.
- `options.get("average", False)` decides whether to display the average.
(continued below 👇)
3. The three calls demonstrate flexibility:
- The first call passes four numbers and two keywords (
- The second call passes only numbers, relying on default keyword values.
- The third call passes nothing;
Key take‑aways
-
-
- The same
- Using these constructs lets you write highly reusable and extensible functions without fixing the exact number or names of arguments up front.
- The first call passes four numbers and two keywords (
prefix and average). - The second call passes only numbers, relying on default keyword values.
- The third call passes nothing;
numbers becomes an empty tuple, options an empty dict, and the function still runs safely.Key take‑aways
-
*args packs extra positional arguments into a tuple. -
**kwargs packs extra keyword arguments into a dictionary. - The same
* and ** symbols unpack a sequence or mapping when calling a function. - Using these constructs lets you write highly reusable and extensible functions without fixing the exact number or names of arguments up front.
What type does Python store extra positional arguments in when using *args?
Anonymous Quiz
0%
list
100%
tuple
0%
set
0%
dict