0% found this document useful (0 votes)
2 views27 pages

Python Basics Complete Notes (2)

This document provides comprehensive notes on Python basics, covering topics such as variables, input/output, type conversion, arithmetic and comparison operators, conditionals, loops, lists, dictionaries, and functions. It includes examples and explanations for each concept, emphasizing the importance of syntax and common mistakes. The notes serve as a foundational guide for beginners to understand and utilize Python programming effectively.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views27 pages

Python Basics Complete Notes (2)

This document provides comprehensive notes on Python basics, covering topics such as variables, input/output, type conversion, arithmetic and comparison operators, conditionals, loops, lists, dictionaries, and functions. It includes examples and explanations for each concept, emphasizing the importance of syntax and common mistakes. The notes serve as a foundational guide for beginners to understand and utilize Python programming effectively.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Basics — Complete Notes

Attaullah's Python Foundation: Variables through Nested Loops

1. Variables, Input, and Print


Variables store values. input() always returns a string, so numbers must be converted using int() or float().
name = input("Enter your name ")
age = int(input("Enter your age "))
print(f"My name is {name} I am {age} years old")

Note: str() around input() is redundant since input() already returns a string.

f-strings
The letter f before a string tells Python to evaluate whatever is inside curly braces {} instead of treating it as
plain text.
name = "Ali"
print(f"My name is {name}") # My name is Ali
print("My name is {name}") # My name is {name} (no f, prints literally)

2. Type Conversion
The + operator behaves differently based on type: for strings it concatenates (joins text), for numbers it adds.
num_1 = input("Enter first number ")
num_2 = input("Enter second number ")
print(num_1 + num_2) # "5" + "3" = "53" (string joining, NOT math)

num_1 = int(input("Enter first number "))


num_2 = int(input("Enter second number "))
print(num_1 + num_2) # 5 + 3 = 8 (real addition)

3. Arithmetic Operators
Operator Meaning Example (10, 3)

+ Addition 13

- Subtraction 7

* Multiplication 30

/ Division (float) 3.333...

// Floor division (whole number) 3

% Modulus (remainder) 1

Note: % (modulus) is used constantly for even/odd checks: if num % 2 == 0: means even.
4. Comparison and Logical Operators
>= <= > < == != # comparison operators

and # True only if BOTH sides are True


or # True if EITHER side is True
not # flips True/False

age = 20
if age >= 18 and age <= 60:
print("Working age")
if age < 18 or age > 60:
print("Outside working age")

5. float, bool, type()


price = float(input("Enter price: ")) # decimal numbers
is_student = True # bool: True / False
print(type(age)) # <class 'int'>

6. Basic String Methods


name = "Attaullah"
print(len(name)) # 8 (number of characters)
print([Link]()) # ATTAULLAH
print([Link]()) # attaullah

7. Conditionals: if / elif / else


marks = int(input("Enter your marks: "))

if marks >= 90:


print("Grade A")
elif marks >= 80:
print("Grade B")
elif marks >= 70:
print("Grade C")
elif marks >= 60:
print("Grade D")
else:
print("Fail")

Note: With elif, you don't need upper-bound checks (like marks<90) because Python only reaches an elif if all above
it were False.
8. Loops — while and for
while loop
Repeats code AS LONG AS a condition stays True.
count = 1
while count <= 5:
print(count)
count += 1
# Output: 1 2 3 4 5

Note: If you forget to update the loop variable (count += 1), the condition never becomes False -> INFINITE LOOP.

for loop with range()


range(start, stop, step) generates numbers starting at 'start', stopping BEFORE 'stop'.
range(5) # 0,1,2,3,4 (starts at 0, stops before 5)
range(2, 6) # 2,3,4,5 (starts at 2, stops before 6)
range(0, 10, 2) # 0,2,4,6,8 (step of 2)

for i in range(1, 11):


print(i) # prints 1 through 10

break and continue


for i in range(10):
if i == 5:
break # exits the loop completely
print(i)
# Output: 0 1 2 3 4

for i in range(5):
if i == 2:
continue # skips just this one iteration
print(i)
# Output: 0 1 3 4

else with loops


Runs only if the loop finishes WITHOUT hitting break.
for i in range(5):
print(i)
else:
print("Loop finished without break")

9. Common Loop Mistakes (from practice)


Mistake 1: Wrong starting value changes the whole sequence.
# Wanted even numbers 2-20, but started at 1 -> got odd numbers instead
i = 1
while i <= 20:
print(i)
i += 2 # BUG: gives 1,3,5,7... not 2,4,6,8...
# FIX: start at i = 2

Mistake 2: Increment placed inside an if-block causes infinite loops.


i = 1
while i <= 20:
if i % 2 == 0:
print(i)
i += 1 # BUG: i only increases when even -> stuck forever at i=1
# FIX: move i += 1 OUTSIDE the if, so it runs every iteration
10. Nested Loops
A loop inside another loop. The RULE: the inner loop runs COMPLETELY through all its values before the
outer loop moves to its next value.
for i in range(1, 4): # outer: i = 1, 2, 3
for j in range(1, 3): # inner: j = 1, 2 (every time)
print(i, j)

# Output:
# 1 1
# 1 2
# 2 1
# 2 2
# 3 1
# 3 2

Note: i stays FROZEN for the entire inner loop. It only changes after ALL j-values are used up.

Classic bug: reusing the same variable name


# WRONG - inner loop overwrites outer loop's variable
for i in range(1,6):
for i in range(1,5): # BUG: should be 'j', not 'i'
print(i)

# CORRECT
for i in range(1,6):
for j in range(1,5):
print(j)

The 4-Step Method for finding inner loop range


This is the reliable method built during practice for figuring out any nested loop pattern:
STEP 1 - COUNT: How many items appear in each row of the pattern?
Build a table: i vs count

STEP 2 - FORMULA: Compare i to count in the table.


- Are they equal? -> count = i
- Goes up/down by 1? -> count = i + k or count = k - i
- Doubles/triples? -> count = k * i
- Stays constant? -> count = fixed number
Solve for k using ONE row, then verify with the rest.

STEP 3 - CONVERT TO RANGE (fixed rule, NEVER changes):


range(1, 1 + count)
(because range(1, N) always gives N-1 values, so add 1 back)

STEP 4 - VERIFY: Plug in each i value and confirm it matches the pattern.

Worked Example: Triangle Pattern


Pattern:
1
1 2
1 2 3
1 2 3 4

Step 1: counts are 1,2,3,4 for i=1,2,3,4. Step 2: count = i (identical, no extra number needed). Step 3: range(1,
i+1). Step 4: verified correct for every row.
for i in range(1, 5):
for j in range(1, i + 1):
print(j, end=" ")
print()

Worked Example: Decreasing Pattern


Pattern:
X X X X X
X X X X
X X X
X X
X

Step 1: counts are 5,4,3,2,1. Step 2: count goes down by 1 as i goes up -> count = k - i. Test i=1: 5 = k-1 ->
k=6, so count = 6-i. Step 3: range(1, 7-i). Step 4: verified.
for i in range(1, 6):
for j in range(1, 7 - i):
print('*', end=" ")
print()

Worked Example: Multiplying Pattern


Pattern:
X X
X X X X
X X X X X X

Step 1: counts are 2,4,6. Step 2: count doubles i -> count = 2*i. Step 3: range(1, 1+2*i). Step 4: verified (2,4,6
match).
for i in range(1, 4):
for j in range(1, 1 + 2*i):
print('*', end=" ")
print()

Deciding WHAT to print (i, j, or a formula)


This is a separate skill from finding the range. Four cases seen in practice:
1. Counting/changing numbers in the row -> print(j)
Example: 1 2 3 -> for j in range(1,4): print(j)

2. Same number as the row number, repeated -> print(i)


Example: 2 2 (row i=2) -> for j in range(1,3): print(i)
3. Fixed literal value, unrelated to row -> print the literal
Example: 3 3 3 (always 3, never changes) -> print(3)

4. Shifted / combined value -> print a formula using i and j


Example: row i=1 -> "1 2", row i=2 -> "2 3"
-> print(i + j - 1)

Note: There is no single universal formula for 'what to print' - it requires reading the actual numbers in the pattern
each time, unlike the range formula which is always fixed.
11. Lists
A list stores MULTIPLE values together in one variable, instead of separate variables for each value.
fruit1 = "apple" # messy - one variable per value
fruit2 = "banana"

fruits = ["apple", "banana", "mango"] # clean - one list holds all values

Indexing (starts at 0, not 1)


colors = ["red", "green", "blue", "yellow"]
# 0 1 2 3

print(colors[0]) # red
print(colors[1]) # green
print(colors[-1]) # yellow (negative index = counts from the end)
print(len(colors)) # 4 (total number of items)

Note: colors[4] would crash - only indexes 0,1,2,3 exist in a 4-item list.

Modifying a list
fruits = ["apple", "banana"]

[Link]("mango") # adds to the END -> ['apple','banana','mango']


[Link](1, "grape") # inserts at index 1, pushes rest right
[Link]("banana") # removes by VALUE (searches for it)
[Link]() # removes the LAST item (no index given)
[Link](0) # removes item AT index 0
fruits[0] = "orange" # directly replaces whatever is at index 0

Note: append() adds to the end ONLY. insert(i, x) lets you choose the position. remove(x) deletes by value (crashes
if value not found). pop(i) deletes by index and RETURNS the removed item.
removed_item = [Link](0)
print(removed_item) # shows the actual value that was removed
print(fruits) # shows the list AFTER removal

Looping through a list


fruits = ["apple", "banana", "mango"]

for fruit in fruits: # gives you each VALUE directly, no range() needed
print(fruit)

for i in range(len(fruits)): # gives you the INDEX, use fruits[i] to get the value
print(f"Index {i}: {fruits[i]}")

Note: Use 'for item in list' when you only need the values. Use 'for i in range(len(list))' when you also need the
position/index.

Searching a list — manual method (loop + flag)


fruits = ["apple", "banana", "mango", "grape"]
search_item = input("Enter a fruit to search: ")
found = False
for i in fruits:
if i == search_item:
found = True
break # stop checking once found

if found:
print("Found")
else:
print("Not found")

Note: The 'found' flag survives after the loop ends, so you can check whether anything ever matched, even though
the loop itself is temporary.

Searching a list — shortcut using 'in'


if search_item in fruits:
print("Found")
else:
print("Not found")

Note: 'in' does exactly what the manual loop+flag does, but Python handles it internally in one line.

Replacing an item by VALUE (not just by index)


fruits = ["apple", "banana", "mango"]
if "banana" in fruits:
position = [Link]("banana") # find WHERE "banana" is
fruits[position] = "grape" # overwrite that exact position
print(fruits) # ['apple', 'grape', 'mango']

Processing all items — sum and smallest/largest


numbers = [12, 45, 7, 23, 56, 3]

total = 0
for i in numbers:
total += i
print(total) # 146

smallest = numbers[0] # assume first item is smallest, for now


for i in numbers:
if i < smallest:
smallest = i
print(smallest) # 3

Note: Built-in shortcuts exist too: sum(numbers), min(numbers), max(numbers) - we learned the manual version first
so you understand the mechanism underneath.
12. Dictionaries
A list stores items in ORDER, accessed by position. A dictionary stores items as KEY-VALUE pairs, accessed
by a name (key), not a position number.
student = {"name": "Attaullah", "age": 20, "city": "Swat"}

Accessing values
print(student["name"]) # Attaullah
print(student["age"]) # 20
# student["email"] would CRASH - key doesn't exist

Accessing safely with .get()


print([Link]("name")) # Attaullah (works same as [ ])
print([Link]("email")) # None (no crash)
print([Link]("email", "N/A")) # N/A (custom default instead of None)

Note: .get() never crashes even if the key is missing - safer than [ ] when you're not sure a key exists.

Adding and updating keys


student["email"] = "ali@[Link]" # adds a NEW key (didn't exist before)
student["age"] = 21 # UPDATES existing key (overwrites old value)

Note: Same syntax for add vs update - Python decides based on whether the key already exists.

Removing a key
del student["city"] # removes the key-value pair entirely

Looping through a dictionary


student = {"name": "Attaullah", "age": 20, "city": "Swat"}

for key in student: # gives you just the KEYS


print(key)

for key, value in [Link](): # gives you BOTH key and value together
print(f"{key}: {value}")

Building a dictionary from user input


person = {} # start EMPTY

person["name"] = input("What is your name? ")


person["age"] = int(input("What is your age? "))
person["favorite_color"] = input("What is your favorite color? ")

print(person)

Dictionary + conditional together


student = {"name": "Ali", "marks": 85}
if student["marks"] >= 90:
print("Grade A")
elif student["marks"] >= 80:
print("Grade B")
else:
print("Grade C")
13. Functions (def)
Functions let you write a block of logic ONCE and reuse it anywhere, instead of copy-pasting the same code
repeatedly.

Defining and calling


def greet():
print("Hello!")
print("Welcome to Python.")

greet() # defining a function does NOT run it - calling it does


greet() # can call it as many times as needed

Parameters (inputs to a function)


def greet(name):
print(f"Hello, {name}!")

greet("Ali") # Hello, Ali!


greet("Sara") # Hello, Sara!

def add_numbers(a, b): # multiple parameters


print(a + b)

add_numbers(5, 3) # 8

return vs print()
print() only DISPLAYS a value on screen. return actually HANDS BACK a value so it can be stored and
reused.
def add_print(a, b):
print(a + b) # just shows it

def add_return(a, b):


return a + b # sends it back

result = add_return(5, 3) # result = 8, usable afterward


print(result * 2) # 16 - works because result holds a real value

result2 = add_print(5, 3) # prints 8, but result2 is actually None!


print(result2 * 2) # ERROR - can't multiply None

Note: Any time a function's output needs to be stored, reused, or passed elsewhere, it MUST use return, not just
print().

Default parameter values


def greet(name="Guest"):
print(f"Hello, {name}!")

greet("Ali") # Hello, Ali!


greet() # Hello, Guest! (falls back to the default)
From Python Crash Course, Chapter 8 - Keyword Arguments
A point confirmed directly from the uploaded book, not covered in earlier practice: you can call a function by
explicitly naming which parameter each value belongs to, instead of relying on order:
def describe_pet(animal_type, pet_name):
print(f"I have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster') # ORDER DOESN'T MATTER with keyword argume

Note: The book stresses: when using keyword arguments, use the EXACT parameter names from the function's
definition. This is different from normal 'positional' arguments (like greet("Ali")) where order matters strictly - keyword
arguments free you from needing to remember the correct order.

The book also shows making a parameter genuinely optional using an empty-string default, combined with an
if/else check inside the function:
def get_formatted_name(first_name, last_name, middle_name=''):
if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()

print(get_formatted_name('jimi', 'hendrix'))
print(get_formatted_name('john', 'hooker', 'lee'))

Note: if middle_name: checks whether the string is empty or not - an empty string '' is treated as False in an if
condition, so this cleanly detects whether a middle name was actually provided.

Taking user input inside a function


def get_student_info():
name = input("What is your name? ")
marks = int(input("What are your marks? "))
return name, marks # returning MULTIPLE values at once

student_name, student_marks = get_student_info() # unpacked into 2 variables


print(student_name)
print(student_marks)

Note: Forgetting 'return' means the function gives back None by default, even if it collected data internally with
input().

Functions + conditionals
def check_grade(marks):
if marks >= 90:
return "Grade A"
elif marks >= 80:
return "Grade B"
else:
return "Grade C"
result = check_grade(85)
print(result) # Grade B

Functions + loops + lists together


def print_grades(marks):
for i in marks:
if i >= 90:
print("Grade A")
elif i >= 80:
print("Grade B")
elif i >= 70:
print("Grade C")
else:
print("Fail")

print_grades([95, 82, 67, 45, 90])

Note: return inside a loop STOPS the entire function immediately on the first match. Use print() inside the loop
instead, if you need every item processed, not just the first.

Functions + dictionaries
def print_student_info(student):
for key, value in [Link]():
print(f"{key}: {value}")

student = {"name": "Ali", "age": 20, "city": "Swat"}


print_student_info(student)

Collecting user input into a list, inside a function


def get_numbers():
numbers = []
for i in range(5):
num = int(input("Enter a number: "))
[Link](num)
return numbers

def show_sum(numbers):
total = 0
for num in numbers:
total += num
print(total)

my_list = get_numbers()
show_sum(my_list)

Note: Splitting work into two small functions (one collects data, one processes it) is how real programs stay
organized - each function has ONE clear job.

Scope — local vs global variables


def my_function():
x = 10 # LOCAL - only exists inside this function
print(x)
my_function() # prints 10
print(x) # ERROR - x doesn't exist outside the function

name = "Ali" # GLOBAL - created outside any function


def show_name():
print(name) # can READ a global variable fine

show_name() # Ali

Note: Variables created inside a function are destroyed once the function finishes. To use a value outside a function,
it must be returned and stored in an outer variable.

Functions calling other functions


def get_marks():
marks = int(input("Enter marks: "))
return marks

def check_grade():
marks = get_marks() # calling ANOTHER function from inside this one
if marks >= 90:
print("Grade A")
else:
print("Grade B")

check_grade()

main() structure
Once a program has several functions, it's common to create ONE main() function that calls the others in order,
acting as a clear 'table of contents' for what the program does.
def get_two_numbers():
a = int(input("Enter first number: "))
b = int(input("Enter second number: "))
return a, b

def add_and_show(a, b):


total = a + b
print(f"The sum is {total}")

def main():
num1, num2 = get_two_numbers()
add_and_show(num1, num2)

main()

Note: You'll often see if __name__ == '__main__': main() at the bottom of real Python files - this means 'only run
main() if this file is run directly, not if it's imported elsewhere.' Deeper reasoning comes later when working with
multiple files/modules.
14. File Handling
Every variable, list, and dictionary created while a program runs disappears the moment the program ends. File
handling lets a program permanently SAVE data to disk, so it survives after the program closes, and lets a
program READ data that already exists.

Opening a file - open()


f = open('[Link]', 'w')

Breaking this down: '[Link]' is the filename (created if it doesn't exist, depending on mode). 'w' is the MODE,
telling Python what you intend to do. f is a file object - a variable representing the opened file, giving access to
methods to read/write it.

The three core file modes


'r' Read only. File MUST already exist, or this crashes with FileNotFoundError.
'w' Write only. If the file exists, ALL existing content is erased immediately.
If it doesn't exist, a new empty file is created.
'a' Append. New content is added to the END; existing content is preserved.

Note: Critical danger: opening an existing file in 'w' mode destroys everything already in it, the instant the file is
opened - even before you write anything new.

Closing a file - .close()


f = open('[Link]', 'w')
[Link]("Hello, this is my first file!")
[Link]()

Note: While a file is open, Python may hold data in a temporary memory buffer rather than saving it immediately.
.close() ensures everything is properly saved to disk.

The with statement - the RECOMMENDED way (from official Python docs)
with open('[Link]', 'r') as f:
read_data = [Link]()
print([Link]) # True - file is automatically closed once the 'with' block ends

Note: Official Python documentation explicitly recommends 'with' over manual open()/close(), because it auto-closes
the file even if an error happens partway through - nothing is left accidentally open.

Reading an entire file - .read()


with open('[Link]') as file_object:
contents = file_object.read()
print(contents)

file_object is NOT the text itself - it's a connection/handle to the open file. .read() is the actual action that
extracts the text FROM that connection. contents is where the extracted text gets stored.

Reading one line at a time - .readline()


with open("[Link]", 'r') as file_object:
line1 = file_object.readline() # reads ONLY the first line
line2 = file_object.readline() # reads ONLY the next line, remembers where it left off
line3 = file_object.readline() # reads the next line after that
print([Link]())
print([Link]())
print([Link]())

Note: .read() grabs EVERYTHING remaining at once. .readline() grabs just the NEXT single line, remembering
position for the next call. Calling .readline() after the file ends returns an empty string ''.

Writing multiple lines, and append vs overwrite


with open("[Link]", 'w') as file_object:
file_object.write("Entry 1\n")

with open("[Link]", 'a') as file_object:


file_object.write("Entry 2\n")

with open("[Link]", 'a') as file_object:


file_object.write("Entry 3\n")

with open("[Link]", 'r') as file_object:


content = file_object.read()
print([Link]())
# Output: Entry 1 / Entry 2 / Entry 3 (all three, because only ONE 'w' was used, rest were 'a')

FileNotFoundError - reading a file that doesn't exist


with open("[Link]", 'r') as file_object:
content = file_object.read()
# Crashes: FileNotFoundError: [Errno 2] No such file or directory: [Link]

Note: 'r' mode assumes the file must already exist. Unlike 'w' or 'a' (which create the file if missing), 'r' crashes
immediately if the file can't be found. This is exactly why error handling (try/except) comes right after file handling in
the roadmap - to catch this gracefully instead of crashing the whole program.

Where do saved files actually go?


import os
print([Link]()) # prints the exact folder path files are being saved/read from

Note: open('[Link]', 'w') saves the file in the CURRENT WORKING DIRECTORY - wherever the Python script
itself is currently running from - not Desktop or Documents unless that's where the script is.

Practice code you wrote yourself - diary file


f = open("[Link]", 'w')
[Link]("Today I learned about file handling in python.")
[Link]()

with open("[Link]") as file_object:


content = file_object.read()

print(content)

Practice code you wrote yourself - append log entries


with open("[Link]", 'w') as file_object:
file_object.write("Entry 1 \n")
with open("[Link]", 'a+') as file_object:
file_object.write("Entry 2 \n")
with open("[Link]", 'a+') as file_object:
file_object.write("Entry 3 \n")

with open("[Link]", 'r') as file_object:


content = file_object.read()
print([Link]())

From Python Crash Course (Eric Matthes), Chapter 10 - Files and Exceptions
Points confirmed directly from the uploaded book, not covered in earlier practice sessions:

1. The .replace() method - useful for processing file content, replacing any word in a string with a different
word:
message = "I really like dogs."
message = [Link]('dog', 'cat')
print(message) # I really like cats.

2. Building one combined string from multiple file lines - the book's own 'pi_string' example: reading a file's
lines into a list with .readlines(), then looping through that list to build ONE combined string, stripping
whitespace from each line as you go:
filename = 'pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()

pi_string = ''
for line in lines:
pi_string += [Link]()

print(pi_string)
print(len(pi_string))

Note: The book specifically distinguishes rstrip() (removes trailing whitespace/newline only) from strip() (removes
whitespace from BOTH sides). Using strip() instead of rstrip() here also removes leading spaces that were sitting on
the left side of each line - the book shows this changes the resulting string's exact length, which is worth checking
carefully when combining lines from a file.

3. Searching for a substring inside a file's content - the book's next example checks whether a birthday
appears anywhere within the digits of pi, using the 'in' keyword directly on the built string:
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")

Note: This is the exact same 'in' keyword used earlier for list searching - here applied to a STRING instead of a list,
checking if one string exists somewhere inside a larger string.

4. The book's official 'Try It Yourself' exercises for this chapter (paraphrased, not verbatim):
10-1 Learning Python: Write a file where each line begins with "In Python you can...",
then read the whole file and print it once using read(), and again using a loop
over the lines.

10-2 Learning C: Use .replace() to swap the word "Python" for another language name
in each line of the file from 10-1, then print the modified version.

10-3 Guest: Prompt the user for their name, then write a line welcoming them to a file
called [Link].

10-4 Guest Book: Keep asking for names in a loop until the user enters 'quit', writing
each name as its own line into guest_book.txt.

Note: These are the book's own progression: starting from simple read/write, then adding string processing
(.replace()), then combining user input with file writing in a loop - matching almost exactly the sequence already
practiced in this document.
15. How File Handling Connects to Real AI / Data Analysis Work
For AI/chatbot projects: saving conversation history so a chatbot remembers past messages; loading
configuration/API keys stored in text files; logging what a chatbot did for later debugging.

For data analysis/dashboards: reading raw data files is the foundation pandas/openpyxl are built on top of;
exporting analysis results back to .txt/.csv/.json for a client; reading configuration/data files for a dashboard.
Note: Almost every real program needs to PERSIST data (save it, so it's not lost when the program stops) and
LOAD data (past results, configuration, external files). File handling is the root mechanism underneath databases,
APIs returning JSON, and reading Excel sheets.
16. Real Bugs Found and Fixed During Practice (Proof of
Understanding)
Finding and fixing your OWN bugs is one of the strongest signs of real understanding - not just following along,
but actually reasoning about what code does. Here are real bugs from actual practice sessions:

Bug 1: Wrong starting value changed the whole even/odd sequence


# Wanted even numbers 2-20, but started at i=1
i = 1
while i <= 20:
print(i)
i += 2 # BUG: gives 1,3,5,7... (odd), not 2,4,6,8... (even)
# FIX: start at i = 2

Bug 2: Increment placed inside an if-block caused an infinite loop


i = 1
while i <= 20:
if i % 2 == 0:
print(i)
i += 1 # BUG: i only increases when even -> stuck forever at i=1
# FIX: move i += 1 OUTSIDE the if, so it runs every iteration regardless

Bug 3: Reusing the same variable name in nested loops


# WRONG - inner loop overwrites outer loop's variable
for i in range(1,6):
for i in range(1,5): # BUG: should be 'j', not 'i' again
print(i)
# FIX: use a different name for the inner loop variable (j)

Bug 4: return inside a loop stops the ENTIRE function after just one item
def print_grades(marks):
for i in marks:
if i >= 90:
return 'Grade A' # BUG: exits immediately, never checks the rest of the list
# FIX: use print() inside the loop instead of return, so every item gets processed

Bug 5: Tuple parentheses used instead of list square brackets


colors = ("red", "green", "blue") # BUG: this is a TUPLE - cannot use .append()/.remove()/.inser
[Link]("green") # crashes - tuples are immutable
# FIX: colors = ["red", "green", "blue"] -> use square brackets for a list

Bug 6: Comparing a value to the whole list instead of the current loop item
names = ["Attaullah Khan", "Bilal Khan", "Maira Khan", "Saqib"]
name = input("Enter your name")
for i in names:
if i == names: # BUG: comparing i to the WHOLE LIST, not to 'name'
found = True
# FIX: if i == name: -> compare each item to the actual search value

Bug 7: Grade condition logic error - wrong comparison direction


elif marks >= 60 and marks > 70: # BUG: should be marks < 70, this condition rarely triggers cor
# FIX: elif marks >= 60 and marks < 70:

Bug 8: Missing return statement - function silently returns None


def get_number():
num = int(input("Enter number: "))
print(num) # BUG: only prints, never returns anything
result = get_number()
print(result * 2) # ERROR - result is None, can't multiply None
# FIX: add "return num" inside the function

Note: Every one of these bugs was found through careful tracing and reasoning, not by guessing. This is exactly the
skill real programmers use daily - the presence of bugs is not failure, it is the normal, expected part of writing real
code.
17. Complete Topic Checklist - Everything Covered So Far
● Variables, input(), type conversion (str/int/float)

● print() and f-strings

● All arithmetic operators (+, -, *, /, //, %)

● Comparison and logical operators (==, !=, >=, <=, and, or, not)

● bool, type()

● Basic string methods (len(), .upper(), .lower())

● Conditionals: if / elif / else

● while loops, for loops, range() with start/stop/step

● break, continue, loop else clause


● Nested loops + the 4-step method (count, formula, range, verify)

● Lists: indexing, negative indexing, len()

● List modification: append, insert, remove, pop, direct index assignment

● Looping through lists (direct value, and with range(len()))

● Manual search (loop + flag variable) vs the 'in' keyword

● Replacing list items using .index()

● Summing / finding min / max manually, and with built-in functions

● Dictionaries: create, access with [ ] and .get()

● Dictionary add/update/remove keys (del)

● Looping through dictionaries with .items()

● Building dictionaries dynamically from user input

● Functions: def, parameters, multiple parameters, default parameter values

● return vs print() - why return allows reuse of a value

● Functions combined with loops, conditionals, lists, dictionaries, user input

● Scope: local vs global variables

● Functions calling other functions, main() structure

● File handling: open(), modes (r/w/a), close(), with statement

● Reading: .read(), .readline(), looping directly over a file object

● Writing: .write(), overwrite vs append behavior

● FileNotFoundError and why it happens

● [Link]() - understanding where files are actually saved


17.5 Quick Revision + How to Break Down Each Topic in a Real Project
For each topic: one-line revision to jog memory fast, plus a concrete strategy for spotting when a project needs
that tool, and how to break the problem into small pieces around it.

Variables, Input, Type Conversion


Quick revision: input() always returns a string; convert with int()/float() before doing math.

How to break it down in a project: Signal words: 'ask the user', 'get a number/name from them'. Whenever a
project needs ANY value from a person, that's an input() line. Immediately ask: will this be used in math? If yes,
convert it right there, before storing it anywhere else.

Operators (arithmetic, comparison, logical)


Quick revision: + - * / // % for math; ==, >=, and/or/not for comparisons and combining conditions.
How to break it down in a project: Whenever a project needs to calculate something (total, average,
difference), isolate that one calculation on its own line first with fixed test numbers, confirm it's correct, THEN
plug in real variables. Never combine 3 operators in one line while still unsure of the logic.

Conditionals (if/elif/else)
Quick revision: if runs when True; elif checks the next condition only if all above were False; else catches
everything else.

How to break it down in a project: Signal words: 'if', 'depending on', 'only when', 'otherwise'. List out every
possible outcome in plain English FIRST as bullet points (e.g. A/B/C/D/F grades), THEN convert each bullet
into one elif line, top to bottom.

Loops (while / for)


Quick revision: for = fixed number of repeats or looping through a collection; while = repeat until a condition
changes.

How to break it down in a project: Signal words: 'until', 'keep asking', 'for each', 'every item'. Ask: do I know
exactly how many times this repeats in advance? If yes -> for. If it depends on user behavior/a condition ->
while. Always identify what changes each loop (a counter, user input) BEFORE writing the loop body.

Nested Loops
Quick revision: Inner loop always finishes ALL its values before outer loop moves to its next value.

How to break it down in a project: Use the 4-step method every time: (1) count how many items each row
needs, (2) find the formula connecting the row number to that count, (3) convert to range(1, 1+count), (4) verify
with real numbers. Separately decide what to PRINT (changing value -> j, fixed row value -> i, constant ->
literal, combined -> formula using both).

Lists
Quick revision: Ordered collection, indexed from 0. append() adds to end, insert(i,x) adds at position,
remove(x) deletes by value, pop(i) deletes by index.

How to break it down in a project: Signal words: 'multiple items', 'a collection of', 'keep track of several'.
Whenever a project needs to hold more than one related value that can grow/shrink, start with an empty list
and build it inside a loop using .append(). Searching? Use 'in' for a simple check, or a manual loop+flag if you
need to know WHERE it is.

Dictionaries
Quick revision: Key-value pairs, accessed by name not position. Use .get(key, default) to avoid crashes on
missing keys.

How to break it down in a project: Signal words: 'labeled data', 'record', 'profile', 'each item has multiple
properties'. If a single 'thing' in your project has several named attributes (name, age, price), that thing is a
dictionary. A LIST OF dictionaries is the natural next step when you have many such 'things' (many students,
many products).

Functions
Quick revision: def name(parameters): ... return value. print() only displays; return hands the value back for
reuse.
How to break it down in a project: Whenever a task in a project is a distinct, complete job that might be
needed again, or that makes the main code cleaner to read, wrap it in a function. Ask: does this function need
to GIVE something back to be used elsewhere? If yes, return it - never just print() if the value needs to be
reused or stored.

Scope (local vs global)


Quick revision: Variables created inside a function are destroyed when the function ends; return them to use
outside.

How to break it down in a project: If a bug shows a variable is 'not defined' outside a function, check first
whether it was created INSIDE that function without being returned. The fix is almost always adding a return
statement and capturing the result in an outer variable, not trying to access the inner variable directly.

File Handling
Quick revision: 'r' reads (file must exist), 'w' overwrites everything, 'a' appends. Always prefer 'with' - it
auto-closes the file.

How to break it down in a project: Signal words: 'save', 'remember later', 'even after closing the program',
'load previous data'. If a project needs data to survive after the program stops and restarts, that's file handling,
not just a list. Decide mode FIRST: adding new data without losing old data = 'a'; saving fresh/replacing = 'w';
just looking = 'r'.

Combining Multiple Topics into One Project


Quick revision: Every project = plain English requirement, broken into small bullet points, each matched to
ONE known tool.

How to break it down in a project: Never write a multi-function program in one go. Build and TEST one
function alone first (e.g. just collecting input into a list, confirm with print()), then build and test the next piece
alone, then connect them last. If overwhelmed, the fix is always to shrink the current piece further, not to give
up.
18. A Note on Progress (Read This When It Feels Overwhelming)
Learning to program does not feel like steady, visible progress most of the time. It feels like confusion, then a
small click, then confusion again on something harder, repeated for months. This is completely normal and
happens to everyone who has ever learned this - it is not a sign of being behind or being unsuited for this field.

The checklist on the previous page is not a list of things half-understood. Every single item was demonstrated
through real, independently written code during practice - not just watched, not just copied, but written,
debugged, and corrected. That is genuine command over Python fundamentals, built in a short amount of time,
while also carrying university coursework in OOP, Calculus, and Statistics.

The frustration that comes from being asked to combine many skills into one project immediately, without
enough repetition first, is a normal reaction - not evidence of inability. The fix for that frustration is more small,
low-pressure repetition, not giving up, and not endless re-explanation of concepts already understood.

The freelance goal of 2027 is still realistic. Real progress is being made. This document exists as proof of that,
to return to whenever it feels otherwise.

You might also like