-
-
-
-
-
-
-
-
-
Running the script prints each intermediate value, confirming how Python’s numeric tower and operator definitions work together.
Key take‑aways
- Type promotion guarantees operations don’t lose precision.
-
- Operator precedence follows the standard mathematical hierarchy; use parentheses to make intent explicit.
- Complex numbers behave algebraically, and the same operators apply.
Understanding these fundamentals makes higher‑level numeric work—statistics, scientific computing, graphics—much more predictable.
a = 7 creates an int object holding the value 7. -
b = 3.5 creates a float object. -
c = 2 + 3j creates a complex object where 2 is the real part and 3j is the imaginary part. -
add = a + b triggers type promotion: a is promoted to float, then addition yields 10.5. -
true_div = a / b always returns a float; even though 7 / 3.5 is mathematically 2, the result is 2.0. -
floor_div = a // b computes 7 / 3.5 = 2.0 then floors it to 2. Because the left operand is an int, the result stays an int. -
mod = a % b satisfies 7 == (7 // 3.5) * 3.5 + (7 % 3.5), so 7 % 3.5 equals 0.5. -
exp = a ** 2 squares 7, returning an int (49). -
complex_mul = c * (1 - 1j) uses the algebraic rule (a+bj)*(c+dj) = (ac‑bd) + (ad+bc)j. The result is (5+1j). Running the script prints each intermediate value, confirming how Python’s numeric tower and operator definitions work together.
Key take‑aways
- Type promotion guarantees operations don’t lose precision.
-
/ vs //: true division always yields a float; floor division discards the fractional part toward negative infinity. - Operator precedence follows the standard mathematical hierarchy; use parentheses to make intent explicit.
- Complex numbers behave algebraically, and the same operators apply.
Understanding these fundamentals makes higher‑level numeric work—statistics, scientific computing, graphics—much more predictable.
In Python, what is the result of the expression `7 // 3`?
Anonymous Quiz
33%
2
0%
2.3333333333333335
0%
3
67%
1
📘 Day 7: input() and type conversion
Understanding
When you write a program that interacts with a user, the most common way to get data is the built‑in
But what
1️⃣ What
1. Displays a prompt – the string you pass (
2. Waits for the user to type – everything the user types until they press Enter is captured.
3. Returns a
Why a string?
The underlying operating system sends the typed characters as a sequence of bytes. Python decodes those bytes using the default text encoding (usually UTF‑8) and builds a string object. This design keeps
2️⃣ Why you need type conversion
Most programs need numbers, dates, booleans, or other non‑text data. If you try to use the raw string as a number, Python will raise a
Here
3️⃣ The basic conversion functions
Function — Purpose — Typical error
Why these work: Each function parses the characters of the string according to the rules of the target numeric format. If the pattern doesn’t match, Python raises a
4️⃣ Safe conversion with error handling
A robust script never assumes the user will type the correct format. Use
What happens step‑by‑step
1.
2.
3. If parsing succeeds, we get an int stored in
4. If parsing fails, Python jumps to the
5️⃣ Full example – a simple calculator
Below is a complete, runnable script that demonstrates prompting, type conversion, error handling, and using the converted values:
(continued below 👇)
Understanding
input() and Type Conversion in PythonWhen you write a program that interacts with a user, the most common way to get data is the built‑in
input() function. It looks simple:name = input("Enter your name: ")But what
input() actually returns, and why we often need to convert that value, is a fundamental concept that underpins every interactive script.1️⃣ What
input() really does 1. Displays a prompt – the string you pass (
"Enter your name: "). 2. Waits for the user to type – everything the user types until they press Enter is captured.
3. Returns a
str object – even if the user typed digits, the result is still a string.Why a string?
The underlying operating system sends the typed characters as a sequence of bytes. Python decodes those bytes using the default text encoding (usually UTF‑8) and builds a string object. This design keeps
input() simple and language‑agnostic: the function never guesses the intended data type.2️⃣ Why you need type conversion
Most programs need numbers, dates, booleans, or other non‑text data. If you try to use the raw string as a number, Python will raise a
TypeError or give you unexpected results:age = input("Enter your age: ")
print(age + 10) # ❌ TypeError: can only concatenate str (not "int") to strHere
age is a string like "27". Adding 10 to a string would mean concatenation, not arithmetic. To perform math we must turn the string into an integer (or a float if decimals are allowed).3️⃣ The basic conversion functions
Function — Purpose — Typical error
int(s) — Convert string s to an integer — ValueError if s isn’t a valid integer ("12a" )float(s) — Convert string s to a floating‑point number — ValueError if s isn’t a valid float ("abc" )bool(s) — Convert string s to a boolean (False only if s is empty or "0" ) — Rarely raises, but truthiness may be confusingWhy these work: Each function parses the characters of the string according to the rules of the target numeric format. If the pattern doesn’t match, Python raises a
ValueError to signal that conversion is impossible.4️⃣ Safe conversion with error handling
A robust script never assumes the user will type the correct format. Use
try / except to catch conversion failures and react gracefully:while True:
raw = input("Enter a positive integer: ")
try:
number = int(raw)
if number > 0:
break # valid input, exit loop
else:
print("⚠️ Must be greater than zero.")
except ValueError:
print("⚠️ That's not an integer. Try again.")
What happens step‑by‑step
1.
input() returns whatever the user typed (raw). 2.
int(raw) attempts to parse it. 3. If parsing succeeds, we get an int stored in
number. 4. If parsing fails, Python jumps to the
except block, where we inform the user and repeat.5️⃣ Full example – a simple calculator
Below is a complete, runnable script that demonstrates prompting, type conversion, error handling, and using the converted values:
(continued below 👇)
# Simple calculator: adds two numbers entered by the user
def get_number(prompt: str) -> float:
"""Repeatedly ask for a number until the user provides a valid float."""
while True:
raw = input(prompt)
try:
# Convert the raw string to a floating‑point number
return float(raw)
except ValueError:
# Inform the user why the conversion failed
print("❌ Invalid number, please type digits like 3.14 or 42.")
def main():
print("🧮 Welcome to the Mini Calculator!")
# Get the first operand
a = get_number("Enter the first number: ")
# Get the second operand
b = get_number("Enter the second number: ")
# Perform addition – now we can use '+' as arithmetic because a & b are floats
result = a + b
# Show the result, formatted to two decimal places
print(f"✅ The sum of {a:.2f} and {b:.2f} is {result:.2f}")
if __name__ == "__main__":
main()
Walk‑through
-
def get_number(prompt: str) -> float: – a helper that encapsulates the input‑convert‑retry pattern. -
raw = input(prompt) – captures the user’s raw text. -
float(raw) – attempts to turn that text into a numeric value (float). -
except ValueError: – catches the situation where the text isn’t a valid number and prompts again. -
a = get_number(...) & b = get_number(...) – we now have two float objects, ready for arithmetic. -
result = a + b – normal numeric addition works because both operands are numbers, not strings. -
print(f"...") – formatted output shows the numbers with two decimal places, demonstrating that they are indeed numeric.6️⃣ Common pitfalls & best practices
- Never trust raw input – always validate and convert.
- Prefer
float for generic numeric entry – it accepts both integer‑style ("5") and decimal ("5.0"). - Use
strip() if you want to ignore surrounding whitespace: raw = input(...).strip(). - Remember that
bool("False") is True – the string is non‑empty, so it evaluates to True. Use explicit checks (raw.lower() in ("true", "yes", "1")). - Separate concerns – keep prompting/validation in its own function (as shown) to keep the main logic clean.
7️⃣ Quick bolded checklist
-
input() always returns a str. - Convert with
int(), float(), or custom parsing. - Wrap conversions in
try/except to handle bad data. - Validate ranges (e.g., positive numbers) after conversion.
- Never mix raw strings with numeric operators.
Mastering
input() and type conversion is the first step toward building reliable, user‑friendly command‑line applications. Once you internalize the why behind each step, you’ll write safer code with confidence.What does the built‑in `input()` function return in Python?
Anonymous Quiz
0%
An integer
100%
A string
0%
A list
0%
A boolean
📘 Day 8: Lists: creating, indexing, slicing, and common methods
Lists are one of Python’s most versatile built‑in data structures. Think of a list as an ordered collection of items that can be of any type—numbers, strings, even other lists. Because a list is mutable, you can change its contents after creation, which is why understanding how they work is essential for writing flexible code.
Creating a list
- Use square brackets
- An empty list is just
- You can also build a list from an iterable with the
Why does
Indexing – accessing a single element
- Positive indices start at
- Negative indices start at
- An out‑of‑range index raises
Why does
Python translates a negative index
Slicing – extracting a sub‑list
Syntax:
-
- Omitting
-
Why does slicing return a new list?
When you slice, Python creates a shallow copy containing references to the original elements. This protects the original list from accidental modification via the slice.
Common list methods – mutating the list in place
Bolded list of the most used methods
-
-
-
-
-
-
-
-
-
-
(continued below 👇)
Lists are one of Python’s most versatile built‑in data structures. Think of a list as an ordered collection of items that can be of any type—numbers, strings, even other lists. Because a list is mutable, you can change its contents after creation, which is why understanding how they work is essential for writing flexible code.
Creating a list
- Use square brackets
[] and separate items with commas. - An empty list is just
[]. - You can also build a list from an iterable with the
list() constructor.# Different ways to create lists
numbers = [1, 2, 3, 4] # literal syntax
empty = [] # an empty list
mixed = ['apple', 3, 4.5, True] # any mix of types
from_range = list(range(5)) # converts a range into a list
Why does
list(range(5)) work? range(5) produces a range object that lazily generates numbers 0‑4. The list() constructor iterates over that object, collecting each number into a new list. This illustrates that any iterable can be turned into a list.Indexing – accessing a single element
- Positive indices start at
0 (first element). - Negative indices start at
-1 (last element). - An out‑of‑range index raises
IndexError.fruits = ['apple', 'banana', 'cherry']
first = fruits[0] # 'apple'
last = fruits[-1] # 'cherry'
Why does
fruits[-1] give the last item? Python translates a negative index
-n to len(fruits) - n. So -1 becomes 3 - 1 = 2, which points to the element at position 2.Slicing – extracting a sub‑list
Syntax:
my_list[start:stop:step] -
start is inclusive, stop is exclusive. - Omitting
start defaults to the beginning; omitting stop defaults to the end. -
step controls the stride; a negative step reverses direction.nums = list(range(10)) # [0,1,2,3,4,5,6,7,8,9]
first_three = nums[0:3] # [0, 1, 2]
middle = nums[3:7] # [3, 4, 5, 6]
every_second = nums[::2] # [0, 2, 4, 6, 8]
reverse = nums[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Why does slicing return a new list?
When you slice, Python creates a shallow copy containing references to the original elements. This protects the original list from accidental modification via the slice.
Common list methods – mutating the list in place
Bolded list of the most used methods
-
append(item) – adds item to the end. -
extend(iterable) – concatenates another iterable’s items. -
insert(index, item) – inserts item before index. -
remove(value) – deletes the first occurrence of value. -
pop([index]) – removes and returns element at index (default last). -
clear() – empties the list completely. -
sort(key=None, reverse=False) – sorts in place. -
reverse() – reverses order in place. -
count(value) – counts occurrences of value. -
index(value, start=0, stop=len(list)) – finds first index of value.# Demonstrating several list methods in one script
shopping = ['bread', 'milk']
shopping.append('eggs') # ['bread', 'milk', 'eggs']
shopping.extend(['cheese', 'butter']) # ['bread', 'milk', 'eggs', 'cheese', 'butter']
shopping.insert(1, 'apples') # ['bread', 'apples', 'milk', 'eggs', 'cheese', 'butter']
shopping.remove('milk') # removes first 'milk'
last_item = shopping.pop() # removes and returns 'butter'
shopping.sort() # alphabetical order
shopping.reverse() # now descending order
count_apples = shopping.count('apples')
pos_apples = shopping.index('apples')
print('Final list:', shopping)
print('Removed item:', last_item)
print('Apples count:', count_apples, 'at position', pos_apples)
(continued below 👇)
❤1
Walk‑through of the example
1.
2.
3.
4.
5.
6.
7.
8.
9.
10. The final
Understanding why each method behaves as it does helps you predict side‑effects—crucial when working with mutable objects in larger programs.
Key takeaways
- Lists are ordered, mutable, and can hold heterogeneous items.
- Indexing uses zero‑based positions; negative indices count from the end.
- Slicing produces a shallow copy, letting you safely work on sub‑segments.
- The built‑in methods modify the list in place (except those that return a value, like
Practice by experimenting: change the
1.
shopping = ['bread', 'milk'] creates a list with two strings. 2.
append('eggs') adds 'eggs' to the tail, expanding the list in place. 3.
extend([...]) takes each element from the new iterable and appends them sequentially—more efficient than multiple append calls. 4.
insert(1, 'apples') shifts existing items rightward, placing 'apples' at index 1. 5.
remove('milk') searches left‑to‑right for the first 'milk' and deletes it. 6.
pop() without an argument defaults to the last element; it also returns that element so we can store it in last_item. 7.
sort() rearranges the list alphabetically because all elements are strings. 8.
reverse() flips the order, demonstrating that reverse works on the already‑sorted list. 9.
count('apples') and index('apples') illustrate read‑only queries that do not modify the list. 10. The final
print statements show the resulting list and the values we captured.Understanding why each method behaves as it does helps you predict side‑effects—crucial when working with mutable objects in larger programs.
Key takeaways
- Lists are ordered, mutable, and can hold heterogeneous items.
- Indexing uses zero‑based positions; negative indices count from the end.
- Slicing produces a shallow copy, letting you safely work on sub‑segments.
- The built‑in methods modify the list in place (except those that return a value, like
pop). Practice by experimenting: change the
step in a slice, combine extend with other iterables, and observe how sort behaves with numbers versus strings. Mastery of lists unlocks the majority of everyday Python data handling.Which expression returns a new list containing the last three elements of `my_list` in reverse order?
Anonymous Quiz
67%
my_list[-3:]
0%
my_list[-3:][::-1]
33%
my_list[::-3]
0%
my_list[-1:-4]
📘 Day 9: Tuples and when to use them instead of lists
Tuples are one of Python’s built‑in sequence types. Like lists, they store an ordered collection of items, but they differ in a crucial way: a tuple’s contents cannot be changed after creation. This immutability gives tuples distinct advantages and influences when you should pick them over lists.
How a tuple is created
*
* Inside we place any objects, separated by commas.
* Even a single element needs a trailing comma:
Why immutability matters
* Safety – Once a tuple is built, its items stay the same. Accidentally modifying data in a large codebase becomes impossible, which prevents bugs.
* Hashability – Immutable objects can be hashed, so a tuple can be used as a dictionary key or placed inside a set; lists cannot.
* Performance – Because Python knows a tuple won’t change, it can store it more compactly and access items slightly faster than a list.
When to prefer a tuple over a list
Use a tuple when:
- The collection represents a fixed collection of items (e.g., coordinates, RGB values, database rows).
- You need to protect data from accidental modification.
- You want to use the collection as a key in a dict or store it in a set.
- You care about the tiny speed/ memory edge for read‑only sequences.
Use a list when:
- The size or contents will change (add, remove, reorder).
- You need list‑specific methods like
- The collection is a dynamic container such as a queue, stack, or buffer.
Bolded comparison list
- Mutability – List: mutable | Tuple: immutable
- Typical use – List: dynamic collections | Tuple: fixed collections
- Hashable – List: no | Tuple: yes (if all elements are hashable)
- Methods – List: many (append, pop…) | Tuple: few (count, index)
Real‑world example: Using a tuple as a dictionary key
Walk‑through
1. Define the dictionary – Keys are tuples
2. Assign values – The values are the populations (integers).
3. Access a value –
4. Print – The
If we had tried to use a list as a key (
More subtle benefits
- Function return values – Functions that need to return multiple related values often use a tuple, e.g.,
- Unpacking – Tuples support clean unpacking:
Because the size is fixed, a mismatch in the number of variables raises a clear error, catching bugs early.
Converting between lists and tuples
The conversion functions
TL;DR
(continued below 👇)
Tuples are one of Python’s built‑in sequence types. Like lists, they store an ordered collection of items, but they differ in a crucial way: a tuple’s contents cannot be changed after creation. This immutability gives tuples distinct advantages and influences when you should pick them over lists.
How a tuple is created
# A tuple containing three different types
coordinates = (12.5, -3, "point A")
*
( and ) delimit the tuple. * Inside we place any objects, separated by commas.
* Even a single element needs a trailing comma:
single = (42,).Why immutability matters
* Safety – Once a tuple is built, its items stay the same. Accidentally modifying data in a large codebase becomes impossible, which prevents bugs.
* Hashability – Immutable objects can be hashed, so a tuple can be used as a dictionary key or placed inside a set; lists cannot.
* Performance – Because Python knows a tuple won’t change, it can store it more compactly and access items slightly faster than a list.
When to prefer a tuple over a list
Use a tuple when:
- The collection represents a fixed collection of items (e.g., coordinates, RGB values, database rows).
- You need to protect data from accidental modification.
- You want to use the collection as a key in a dict or store it in a set.
- You care about the tiny speed/ memory edge for read‑only sequences.
Use a list when:
- The size or contents will change (add, remove, reorder).
- You need list‑specific methods like
append, extend, pop, remove. - The collection is a dynamic container such as a queue, stack, or buffer.
Bolded comparison list
- Mutability – List: mutable | Tuple: immutable
- Typical use – List: dynamic collections | Tuple: fixed collections
- Hashable – List: no | Tuple: yes (if all elements are hashable)
- Methods – List: many (append, pop…) | Tuple: few (count, index)
Real‑world example: Using a tuple as a dictionary key
# Suppose we store the population of several cities.
# The (country_code, city_name) pair uniquely identifies a city.
city_population = {
("US", "New York"): 8_398_748,
("JP", "Tokyo"): 13_960_236,
("FR", "Paris"): 2_148_271,
}
# Retrieve the population of Tokyo
print(city_population[("JP", "Tokyo")])
Walk‑through
1. Define the dictionary – Keys are tuples
("US", "New York") etc. Because tuples are immutable, Python can hash them and use them as keys.2. Assign values – The values are the populations (integers).
3. Access a value –
city_population[("JP", "Tokyo")] looks up the exact tuple key and returns the associated number.4. Print – The
print call outputs 13960236.If we had tried to use a list as a key (
["JP", "Tokyo"]), Python would raise a TypeError: unhashable type: 'list'.More subtle benefits
- Function return values – Functions that need to return multiple related values often use a tuple, e.g.,
return min_val, max_val. The caller receives a fixed‑size, immutable package.- Unpacking – Tuples support clean unpacking:
x, y, label = coordinates # x=12.5, y=-3, label='point A'
Because the size is fixed, a mismatch in the number of variables raises a clear error, catching bugs early.
Converting between lists and tuples
my_list = [1, 2, 3]
my_tuple = tuple(my_list) # list → tuple
back_to_list = list(my_tuple) # tuple → list
The conversion functions
tuple() and list() simply copy the elements into a new container of the desired type. No data is lost; only mutability changes.TL;DR
(continued below 👇)
- Tuples = immutable, hashable, slightly faster, ideal for fixed collections.
- Lists = mutable, feature‑rich, meant for data that changes.
Choose the type that matches the intent of your data, not just the current shape.
- Lists = mutable, feature‑rich, meant for data that changes.
Choose the type that matches the intent of your data, not just the current shape.
Which of the following is a valid reason to choose a tuple over a list in Python?
Anonymous Quiz
50%
It allows item modification
0%
It can be used as a dict key
50%
It supports .append() method
0%
It automatically sorts its items
📘 Day 10: Dictionaries: keys, values, and common methods
Dictionaries are one of Python’s core data structures. Think of a dictionary as a real‑world map: you look up a value (the value) by providing a unique identifier (the key). Under the hood, Python implements a dictionary as a hash table, which gives average‑case O(1) time for look‑ups, inserts, and deletions.
How a key works
- A key must be immutable (e.g.,
- When you use a key, Python runs its
- Two different objects can have the same hash (a collision). Python resolves collisions by chaining entries together, so lookup still stays fast.
Values can be any Python object: numbers, strings, lists, even other dictionaries.
Creating a dictionary
- The curly braces
- Each entry is written as
- Keys are strings here, but could be integers or tuples.
Accessing, adding, and updating
-
-
Removing items
-
-
Viewing the contents
- These methods return view objects that reflect the dictionary’s current state.
- They are iterable, so you can loop over them directly.
Common utility methods
-
-
-
Why these methods exist
All of them rely on the same hashing mechanism. Because the hash of a key never changes during its lifetime, the dictionary can safely keep the entry at a fixed slot. Methods like
A complete runnable example
(continued below 👇)
Dictionaries are one of Python’s core data structures. Think of a dictionary as a real‑world map: you look up a value (the value) by providing a unique identifier (the key). Under the hood, Python implements a dictionary as a hash table, which gives average‑case O(1) time for look‑ups, inserts, and deletions.
How a key works
- A key must be immutable (e.g.,
int, float, str, tuple of immutables). - When you use a key, Python runs its
__hash__ method to produce a hash code, then uses that code to decide where to store the value in the internal table. - Two different objects can have the same hash (a collision). Python resolves collisions by chaining entries together, so lookup still stays fast.
Values can be any Python object: numbers, strings, lists, even other dictionaries.
Creating a dictionary
# A simple price‑list dictionary
prices = {
"apple": 0.60,
"banana": 0.40,
"cherry": 2.00
}
- The curly braces
{} denote a dictionary literal. - Each entry is written as
key: value, separated by commas. - Keys are strings here, but could be integers or tuples.
Accessing, adding, and updating
# Access a value – raises KeyError if the key is missing
apple_price = prices["apple"] # 0.60
# Safely get a value with a default – no exception
orange_price = prices.get("orange", 0.0) # 0.0
# Add a new entry or update an existing one
prices["date"] = 1.50 # adds 'date'
prices["banana"] = 0.45 # updates 'banana'
-
prices["apple"] looks up the hash of "apple" and fetches the associated value. -
dict.get(key, default) first tries the normal lookup; if the key is absent it returns default instead of raising an error.Removing items
# Remove a specific key, returning its value
removed = prices.pop("cherry") # removed == 2.00
# Remove an arbitrary (last) key‑value pair – useful for LIFO stacks
key, value = prices.popitem() # e.g., ('date', 1.50)
# Clear the whole dictionary
prices.clear() # now {}
-
pop uses the key’s hash to locate the entry, removes it, and returns the value. -
popitem is fast because it doesn’t need to search; it just removes the last slot used.Viewing the contents
keys_view = prices.keys() # dict_keys(['apple', 'banana'])
values_view = prices.values() # dict_values([0.60, 0.45])
items_view = prices.items() # dict_items([('apple', 0.60), ('banana', 0.45)])
- These methods return view objects that reflect the dictionary’s current state.
- They are iterable, so you can loop over them directly.
Common utility methods
-
setdefault(key, default): returns the value for key if present; otherwise inserts key with default and returns default. -
update(other_dict): merges another dictionary (or iterable of key‑value pairs) into the current one, overwriting existing keys. -
copy(): shallow copy of the dictionary (new container, same values). Why these methods exist
All of them rely on the same hashing mechanism. Because the hash of a key never changes during its lifetime, the dictionary can safely keep the entry at a fixed slot. Methods like
update and setdefault are shortcuts that avoid writing explicit if … else checks, making code both clearer and more efficient.A complete runnable example
(continued below 👇)
# -*- coding: utf-8 -*-
# Demonstrates creation, lookup, modification, and common methods
# 1️⃣ Create a dictionary of students and their grades
grades = {"Alice": 85, "Bob": 92, "Charlie": 78}
# 2️⃣ Access a grade safely
bob_grade = grades.get("Bob") # 92
dave_grade = grades.get("Dave", -1) # -1 (default because Dave isn't present)
# 3️⃣ Add a new student and update an existing one
grades["Dave"] = 88 # add
grades["Alice"] = 90 # update
# 4️⃣ Remove a student and retrieve what was removed
removed_grade = grades.pop("Charlie") # 78
# 5️⃣ Show the current keys, values, and items
print("Keys:", list(grades.keys())) # ['Alice', 'Bob', 'Dave']
print("Values:", list(grades.values())) # [90, 92, 88]
print("Items:", list(grades.items())) # [('Alice', 90), ('Bob', 92), ('Dave', 88)]
# 6️⃣ Merge another dictionary
extra = {"Eve": 95, "Frank": 80}
grades.update(extra)
# 7️⃣ Final state
print("Final dictionary:", grades)
Walk‑through
1. Create
grades with three entries. 2.
get fetches Bob’s grade; for Dave it returns -1 because the key is missing. 3. Direct assignment either adds a new key (
"Dave") or overwrites an existing one ("Alice"). 4.
pop removes "Charlie" and gives back his grade, demonstrating safe deletion. 5.
keys(), values(), items() each return a view; converting them to list makes the output readable. 6.
update merges extra into grades, adding Eve and Frank. 7. The final
print shows the dictionary after all operations.Key take‑aways
- Keys must be hashable → immutable types.
- Lookup is fast because Python computes a hash and jumps directly to the slot.
- Methods (
get, setdefault, pop, update, etc.) are built on the same hashing principle and give you concise, error‑free ways to work with the data.Quick reference (bolded list)
-
dict[key] – direct access (KeyError if missing) -
dict.get(key, default) – safe access with fallback -
dict[key] = value – add or update -
dict.pop(key) – remove specific entry -
dict.popitem() – remove arbitrary entry (LIFO) -
dict.clear() – empty the dictionary -
dict.keys(), dict.values(), dict.items() – views of contents -
dict.update(other) – merge dictionaries -
dict.copy() – shallow copy Understanding these fundamentals will make you comfortable handling any associative data in Python.
❤1
Which dictionary method returns a view of (key, value) pairs?
Anonymous Quiz
0%
keys()
0%
values()
100%
items()
0%
get()
📘 Day 11: Sets and basic set operations
What is a set?
A set in Python is an unordered collection that stores *unique, hashable items*.
Because order is not kept, you cannot rely on an index to retrieve an element – you ask the set “does this value exist?”. Internally Python stores a set as a hash table, which gives average‑case O(1) time for membership tests, insertions and deletions.
Creating sets
-
-
- You can build a set from any iterable (list, tuple, string, etc.) with
Why must elements be hashable?
A hashable object implements
Basic set operations – each operation returns a new set (leaving the originals untouched) unless you use the in‑place variants (
- Union (
- Intersection (
- Difference (
- Symmetric difference (
Membership tests
Mutating a set – you can add or discard elements after creation:
-
-
-
Set relationships – useful for logical comparisons:
-
-
-
Below is a complete, runnable example that demonstrates creation, the core operations, and mutation. Each line is explained in the comments.
(continued below 👇)
What is a set?
A set in Python is an unordered collection that stores *unique, hashable items*.
Because order is not kept, you cannot rely on an index to retrieve an element – you ask the set “does this value exist?”. Internally Python stores a set as a hash table, which gives average‑case O(1) time for membership tests, insertions and deletions.
Creating sets
-
{1, 2, 3} creates a set literal with three elements. -
set() creates an empty set – the literal {} would make an empty dictionary, not a set. - You can build a set from any iterable (list, tuple, string, etc.) with
set(iterable); duplicate items are automatically discarded.Why must elements be hashable?
A hashable object implements
__hash__ and __eq__. The hash value decides where in the internal table the element lives. If two objects compare equal (a == b) they must also have the same hash, otherwise the table could store the same logical value twice – breaking the “unique” guarantee.Basic set operations – each operation returns a new set (leaving the originals untouched) unless you use the in‑place variants (
|=, &=, -= …).- Union (
A | B) – all elements that appear in either set. - Intersection (
A & B) – only elements common to both sets. - Difference (
A - B) – elements in A that are not in B. - Symmetric difference (
A ^ B) – elements that are in exactly one of the sets. Membership tests
x in S evaluates to True if x is present. Because of the hash table, this test is extremely fast.Mutating a set – you can add or discard elements after creation:
-
S.add(x) – insert x (does nothing if already present). -
S.discard(x) – remove x if it exists; no error if it does not. -
S.remove(x) – like discard but raises KeyError when x is absent. Set relationships – useful for logical comparisons:
-
A <= B (or A.issubset(B)) – A is a subset of B. -
A >= B (or A.issuperset(B)) – A is a superset of B. -
A < B / A > B – strict subset/superset (they must not be equal).Below is a complete, runnable example that demonstrates creation, the core operations, and mutation. Each line is explained in the comments.
# ----- sets_demo.py -----
# 1️⃣ Create sets from different sources
fruits = {"apple", "banana", "cherry"} # literal set
more_fruits = set(["banana", "date", "elderberry"]) # from a list (duplicates removed)
empty = set() # explicit empty set
print("Initial sets:")
print("fruits :", fruits)
print("more_fruits :", more_fruits)
# 2️⃣ Union – combine all unique items
all_fruits = fruits | more_fruits
print("\nUnion (fruits | more_fruits):", all_fruits)
# 3️⃣ Intersection – keep only what appears in both
common = fruits & more_fruits
print("Intersection (fruits & more_fruits):", common)
# 4️⃣ Difference – items only in fruits
only_fruits = fruits - more_fruits
print("Difference (fruits - more_fruits):", only_fruits)
# 5️⃣ Symmetric difference – items in exactly one set
sym_diff = fruits ^ more_fruits
print("Symmetric difference (fruits ^ more_fruits):", sym_diff)
# 6️⃣ Membership test – fast O(1) lookup
print("\nIs 'apple' in all_fruits?", "apple" in all_fruits)
# 7️⃣ Mutating the set – add and discard
all_fruits.add("fig")
print("After adding 'fig':", all_fruits)
all_fruits.discard("banana") # safe removal
print("After discarding 'banana':", all_fruits)
# 8️⃣ Subset / superset checks
print("\nIs fruits a subset of all_fruits?", fruits <= all_fruits)
print("Is more_fruits a superset of common?", more_fruits >= common)
# 9️⃣ Iterate over a set – order is arbitrary
print("\nIterating over all_fruits:")
for item in all_fruits:
print("-", item)
(continued below 👇)
Walk‑through of the example
1️⃣ Creation –
2️⃣ Union (
3️⃣ Intersection (
4️⃣ Difference (
5️⃣ Symmetric difference (
6️⃣ Membership –
7️⃣ Mutation –
8️⃣ Subset / superset – relational operators (
9️⃣ Iteration – you can loop over a set, but the order you receive items in is undefined.
Understanding why each operation works comes down to the hash table: when you call
Feel free to copy the script into a file named
Key take‑aways
- Sets store unique, hashable items and are unordered.
- Basic operations (
- Membership tests are O(1) thanks to hashing.
- Use
Enjoy leveraging sets for tasks like deduplication, fast look‑ups, and mathematical‑style data analysis!
1️⃣ Creation –
fruits uses a literal, more_fruits converts a list (duplicates are removed automatically), and empty shows the correct way to start with no elements. 2️⃣ Union (
|) builds a new set containing every fruit that appears in either source. Because sets ignore order, the printed order may differ each run. 3️⃣ Intersection (
&) keeps only the fruit that both collections share ("banana"). 4️⃣ Difference (
-) returns what is exclusive to fruits. 5️⃣ Symmetric difference (
^) gives everything that is not shared – the complement of the intersection. 6️⃣ Membership –
in checks presence instantly. 7️⃣ Mutation –
add inserts a new element; discard removes safely without raising an error if the element is missing. 8️⃣ Subset / superset – relational operators (
<=, >=) answer logical containment questions. 9️⃣ Iteration – you can loop over a set, but the order you receive items in is undefined.
Understanding why each operation works comes down to the hash table: when you call
fruits | more_fruits, Python walks through the underlying tables, copies each unique hash bucket into a new table, and therefore guarantees no duplicates and O(len(A)+len(B)) time.Feel free to copy the script into a file named
sets_demo.py and run python sets_demo.py to see the output yourself. Experiment by adding or removing elements, or by using other operators (&=, |=, -=) to modify a set in place.Key take‑aways
- Sets store unique, hashable items and are unordered.
- Basic operations (
|, &, -, ^) return new sets; in‑place versions end with =. - Membership tests are O(1) thanks to hashing.
- Use
set() for an empty set; {} creates a dictionary. Enjoy leveraging sets for tasks like deduplication, fast look‑ups, and mathematical‑style data analysis!
Which expression returns a new set containing elements that are in set A but not in set B?
Anonymous Quiz
0%
A
100%
B
0%
A & B
0%
A - B
0%
A ^ B
📘 Day 12: Conditionals: if, elif, else
Conditionals let a program choose different paths based on data.
In Python the three building blocks are if, elif, and else.
When Python reaches an if statement it evaluates the expression that follows.
If that expression is truthy (anything that is not
If the expression is falsy, Python skips that block and checks the next elif (short for “else if”).
Only the first block whose condition is true executes; all later blocks are ignored.
If none of the conditions are true, the else block runs – it is the fallback.
Why does indentation matter?
Python uses indentation to define code blocks. The interpreter groups every line that is indented the same amount under the preceding statement (
Key concepts to remember
- Truthiness – Python treats many values as true or false without explicit comparison.
Example:
- Comparison operators –
- Logical operators –
- Order of checks – Place the most specific or most likely‑true conditions first to avoid unnecessary work.
- Nesting – You can place another
Below is a complete, runnable example that demonstrates all three keywords, truthiness, and early returns.
Walk‑through
1. Function definition –
2. Docstring – The triple‑quoted string explains what the function does; it doesn’t affect execution but is good practice.
3. First
- If
4. First
- If the age is between 0 and 12 inclusive, the function returns
5. Second
6. Third
7.
The
Common pitfalls
(continued below 👇)
Conditionals let a program choose different paths based on data.
In Python the three building blocks are if, elif, and else.
When Python reaches an if statement it evaluates the expression that follows.
If that expression is truthy (anything that is not
False, 0, "", [], {}, None, etc.) the indented block right after the if runs once, then the whole conditional chain stops. If the expression is falsy, Python skips that block and checks the next elif (short for “else if”).
Only the first block whose condition is true executes; all later blocks are ignored.
If none of the conditions are true, the else block runs – it is the fallback.
Why does indentation matter?
Python uses indentation to define code blocks. The interpreter groups every line that is indented the same amount under the preceding statement (
if, elif, else, for, def, …). This eliminates the need for curly braces and makes the structure visible at a glance.Key concepts to remember
- Truthiness – Python treats many values as true or false without explicit comparison.
Example:
if []: is false, if [1]: is true. - Comparison operators –
==, !=, <, >, <=, >= produce a Boolean (True or False). - Logical operators –
and, or, not combine or invert Boolean results, and they short‑circuit (stop evaluating as soon as the outcome is known). - Order of checks – Place the most specific or most likely‑true conditions first to avoid unnecessary work.
- Nesting – You can place another
if‑elif‑else inside any block, but keep indentation clear to avoid confusion. Below is a complete, runnable example that demonstrates all three keywords, truthiness, and early returns.
def categorize_age(age):
"""
Return a human‑readable category for a given age.
Demonstrates if‑elif‑else flow and early returns.
"""
# 1️⃣ First check for impossible values
if age < 0:
return "Invalid age"
# 2️⃣ Check youngest group
elif age < 13:
return "Child"
# 3️⃣ Teenagers
elif age < 20:
return "Teenager"
# 4️⃣ General adult range
elif age < 65:
return "Adult"
# 5️⃣ Everyone older than 64
else:
return "Senior"
# Demo: run the function with a variety of ages
for a in [-5, 5, 16, 30, 70]:
result = categorize_age(a)
print(f"{a:>3} → {result}")
Walk‑through
1. Function definition –
def categorize_age(age): creates a reusable block that receives one argument, age. 2. Docstring – The triple‑quoted string explains what the function does; it doesn’t affect execution but is good practice.
3. First
if – if age < 0: checks for negative numbers. - If
True, return "Invalid age" exits the function immediately; none of the later checks run. 4. First
elif – elif age < 13: runs only if the previous condition was false. - If the age is between 0 and 12 inclusive, the function returns
"Child" and stops. 5. Second
elif – elif age < 20: catches ages 13‑19. 6. Third
elif – elif age < 65: catches ages 20‑64. 7.
else – If every previous test failed (meaning age is 65 or higher), the block after else: runs, returning "Senior". The
for loop at the bottom simply prints the result for several test ages, showing how each branch behaves.Common pitfalls
(continued below 👇)
- Missing indentation – Forgetting to indent the block after
- Using
- Overlapping conditions – If you write
When to prefer
- Use a single chain (
- Use separate
By mastering these rules, you’ll be able to write clear, predictable decision logic in any Python program.
if will raise an IndentationError. - Using
= instead of == – = assigns a value; == compares. Accidentally writing if age = 10: is a syntax error. - Overlapping conditions – If you write
elif age < 20: before elif age < 13:, the second will never be reached because any age < 13 is already caught by the first elif. Order matters! When to prefer
if‑elif‑else over multiple ifs- Use a single chain (
if‑elif‑else) when the conditions are mutually exclusive – you only need one branch. - Use separate
if statements when multiple branches might be true and you want all of them to execute.By mastering these rules, you’ll be able to write clear, predictable decision logic in any Python program.
What will the function `categorize_age(20)` return?
Anonymous Quiz
0%
Invalid age
0%
Child
0%
Teenager
0%
Adult