Python Basics Complete Notes (2)
Python Basics Complete Notes (2)
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)
3. Arithmetic Operators
Operator Meaning Example (10, 3)
+ Addition 13
- Subtraction 7
* Multiplication 30
% 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
age = 20
if age >= 18 and age <= 60:
print("Working age")
if age < 18 or age > 60:
print("Outside working age")
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 i in range(5):
if i == 2:
continue # skips just this one iteration
print(i)
# Output: 0 1 3 4
# 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.
# CORRECT
for i in range(1,6):
for j in range(1,5):
print(j)
STEP 4 - VERIFY: Plug in each i value and confirm it matches the pattern.
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()
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()
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()
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
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"]
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
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.
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.
Note: 'in' does exactly what the manual loop+flag does, but Python handles it internally in one line.
total = 0
for i in numbers:
total += i
print(total) # 146
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
Note: .get() never crashes even if the key is missing - safer than [ ] when you're not sure a key exists.
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
for key, value in [Link](): # gives you BOTH key and value together
print(f"{key}: {value}")
print(person)
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
Note: Any time a function's output needs to be stored, reused, or passed elsewhere, it MUST use return, not just
print().
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.
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
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}")
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.
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.
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 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.
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.
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.
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.
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.
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 ''.
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.
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.
print(content)
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 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 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
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)
● Comparison and logical operators (==, !=, >=, <=, and, or, not)
● bool, type()
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.
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.
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.
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'.
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.