CodeNFacts
CodeHub
Home

All Categories


Sign In
Python · 20 core topics

Learn Python from the ground up

Every core concept explained in plain language, with a runnable example and its actual output — no filler, just what you need to go from syntax to confident code.

Thanks for downloading ! Happy learning ..❤️..
01

Introduction & Syntax

Python reads close to plain English on purpose. There are no curly braces or semicolons — indentation itself defines where a block of code starts and ends, so whitespace is not a style choice, it's syntax.

  • A standard 4-space indent is the convention almost every codebase follows.
  • Comments start with # and run to the end of the line.
  • print() writes to the console and is usually the first function anyone learns.
intro-syntax.py
# This is a comment — Python ignores everything after '#'
print("Hello, CodeNFacts!")
 
if True:
print("This line is indented, so it's inside the if-block")
print("This line is not indented, so it runs regardless")
output
Hello, CodeNFacts!
This line is indented, so it's inside the if-block
This line is not indented, so it runs regardless
02

Variables & Data Types

A variable is just a name pointing at a value in memory. Python is dynamically typed, meaning you never declare a type up front — the type is decided by whatever value you assign, and it can change later.

  • Core built-in types: int, float, str, bool, and None (Python's 'nothing' value).
  • type(x) tells you what a variable currently holds.
  • Reassigning a variable to a new type is completely legal.
variables-types.py
age = 25 # int
price = 9.99 # float
name = "Ada" # str
is_active = True # bool
result = None # None — represents 'no value'
 
print(type(age), type(price), type(name))
 
age = "twenty-five" # totally legal, age is now a str
print(age)
output
<class 'int'> <class 'float'> <class 'str'>
twenty-five
03

Operators

Operators combine values into new ones. Python groups them into arithmetic (math), comparison (produces True/False), logical (combines booleans), and assignment (stores a result back into a variable).

  • // is floor (integer) division; ** is exponentiation.
  • Comparisons like ==, !=, <, > always return a bool.
  • and, or, not work on booleans — and short-circuit for efficiency.
operators.py
a, b = 10, 3
 
print(a + b, a - b, a * b) # 13 7 30
print(a / b) # 3.3333333333333335 (true division)
print(a // b, a % b, a ** b) # 3 1 1000
 
print(a > b and b > 0) # True
print(a == 10 or b == 99) # True
output
13 7 30
3.3333333333333335
3 1 1000
True
True
04

Strings

Strings are ordered, immutable sequences of characters, which means slicing and indexing work like they do on lists — but every 'edit' actually produces a brand-new string rather than modifying the original.

  • f-strings (f"...") are the cleanest way to embed variables in text.
  • Negative indices count from the end: s[-1] is the last character.
  • Common methods: .upper(), .lower(), .strip(), .split(), .replace(), .join().
strings.py
name = "codenfacts"
 
print(name.upper()) # CODENFACTS
print(name[0], name[-1]) # c s
print(name[:4]) # code
print(name.replace("code", "learn"))
 
age = 5
print(f"{name} is {age} years old")
 
words = "python is fun".split(" ")
print(words) # ['python', 'is', 'fun']
print("-".join(words)) # python-is-fun
output
CODENFACTS
c s
code
learnnfacts
codenfacts is 5 years old
['python', 'is', 'fun']
python-is-fun
05

Lists

A list is an ordered, mutable collection — you can add, remove, and change items after creation, and it can hold a mix of types. It's the workhorse data structure for 'a bunch of things' in Python.

  • Indexing and slicing work exactly like strings: list[0], list[-1], list[1:3].
  • .append() adds one item; .extend() merges another list in.
  • List comprehensions (see topic 13) are the idiomatic way to build lists from other data.
lists.py
fruits = ["apple", "banana", "cherry"]
 
fruits.append("date")
print(fruits) # ['apple', 'banana', 'cherry', 'date']
 
fruits[1] = "blueberry"
print(fruits[0:2]) # ['apple', 'blueberry']
 
fruits.remove("cherry")
print(len(fruits)) # 3
print("apple" in fruits) # True
output
['apple', 'banana', 'cherry', 'date']
['apple', 'blueberry']
3
True
06

Tuples

A tuple looks like a list but is immutable — once created, it can't be changed. That makes tuples a good fit for fixed groups of values, like coordinates, and for dictionary keys, which must be hashable.

  • Written with parentheses: point = (3, 4).
  • Unpacking lets you assign each element to a variable in one line.
  • Trying to modify a tuple raises a TypeError.
tuples.py
point = (3, 4)
x, y = point # unpacking
print(x, y) # 3 4
 
coordinates = (0, 0), (1, 1), (2, 4)
for cx, cy in coordinates:
print(f"({cx}, {cy})")
 
# point[0] = 9 # would raise: TypeError: 'tuple' object does not support item assignment
output
3 4
(0, 0)
(1, 1)
(2, 4)
07

Dictionaries

A dictionary stores key-value pairs, giving you near-instant lookup by key instead of scanning by position. As of Python 3.7+, dictionaries also remember insertion order.

  • Access with square brackets: user['name']; use .get() to avoid a KeyError.
  • .keys(), .values(), and .items() let you iterate different views of the data.
  • Keys must be immutable (strings, numbers, tuples) — lists can't be keys.
dictionaries.py
user = {"name": "Ada", "age": 30, "role": "engineer"}
 
print(user["name"]) # Ada
print(user.get("email", "n/a")) # n/a — no KeyError
 
user["email"] = "ada@codenfacts.dev"
for key, value in user.items():
print(f"{key}: {value}")
output
Ada
n/a
name: Ada
age: 30
role: engineer
email: ada@codenfacts.dev
08

Sets

A set is an unordered collection of unique values — duplicates are automatically dropped. Sets are built for membership testing and for the classic math operations: union, intersection, and difference.

  • Create with {1, 2, 3} or set() for an empty one (not {} — that's a dict).
  • in checks are O(1) on average, much faster than checking a list.
  • | is union, & is intersection, - is difference.
sets.py
a = {1, 2, 3, 3, 2}
print(a) # {1, 2, 3} — duplicates removed
 
b = {3, 4, 5}
print(a | b) # {1, 2, 3, 4, 5} union
print(a & b) # {3} intersection
print(a - b) # {1, 2} difference
 
print(2 in a) # True
output
{1, 2, 3}
{1, 2, 3, 4, 5}
{3}
{1, 2}
True
09

Conditionals

if / elif / else routes your program down different paths depending on a condition. Python evaluates conditions top to bottom and runs the first block whose condition is True, skipping the rest.

  • elif chains let you check several conditions without nesting.
  • Any non-empty string, non-zero number, or non-empty collection is 'truthy'.
  • A one-line conditional expression exists too: 'even' if n % 2 == 0 else 'odd'.
conditionals.py
score = 82
 
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
 
print(grade) # B
 
label = "even" if score % 2 == 0 else "odd"
print(label) # even
output
B
even
10

Loops

for loops iterate over a sequence (a list, string, range, etc.) item by item. while loops repeat as long as a condition stays True. break exits a loop early; continue skips to the next iteration.

  • range(start, stop, step) is the classic way to loop a fixed number of times.
  • enumerate() gives you both the index and the value while looping.
  • A while True loop with an internal break is common for 'repeat until' logic.
loops.py
for i in range(3):
print("count:", i)
 
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(index, fruit)
 
n = 0
while n < 5:
if n == 3:
break
print("n is", n)
n += 1
output
count: 0
count: 1
count: 2
0 apple
1 banana
2 cherry
n is 0
n is 1
n is 2
11

Functions

A function packages a block of logic under a name so you can call it instead of repeating code. def defines it, parameters receive input, and return sends a value back to whoever called it.

  • Parameters can have default values: def greet(name="friend").
  • *args collects extra positional arguments; **kwargs collects extra keyword arguments.
  • A function with no return statement implicitly returns None.
functions.py
def greet(name="friend"):
return f"Hello, {name}!"
 
print(greet()) # Hello, friend!
print(greet("Ada")) # Hello, Ada!
 
def total(*numbers):
return sum(numbers)
 
print(total(1, 2, 3, 4)) # 10
 
def describe(**details):
for key, value in details.items():
print(f"{key}: {value}")
 
describe(name="Ada", role="engineer")
output
Hello, friend!
Hello, Ada!
10
name: Ada
role: engineer
12

Lambda Functions

A lambda is a small, unnamed function written in a single expression — no def, no return keyword, just input in, value out. They're most useful as a quick throwaway function passed into something like sorted() or map().

  • Syntax: lambda arguments: expression.
  • Best kept to one line; anything more complex should be a real def function.
  • Commonly paired with sorted(key=...), map(), and filter().
lambdas.py
square = lambda x: x * x
print(square(5)) # 25
 
people = [("Ada", 30), ("Bo", 25), ("Cy", 40)]
people.sort(key=lambda person: person[1])
print(people) # sorted by age
 
nums = [1, 2, 3, 4, 5]
evens = list(filter(lambda n: n % 2 == 0, nums))
print(evens) # [2, 4]
output
25
[('Bo', 25), ('Ada', 30), ('Cy', 40)]
[2, 4]
13

List Comprehensions

A comprehension builds a new list from an existing iterable in one readable line, replacing the common pattern of creating an empty list and appending to it inside a for loop.

  • Basic form: [expression for item in iterable].
  • Add a condition to filter: [x for x in items if x > 0].
  • The same pattern works for dicts {k: v for ...} and sets {x for ...}.
comprehensions.py
squares = [n * n for n in range(6)]
print(squares) # [0, 1, 4, 9, 16, 25]
 
evens = [n for n in range(10) if n % 2 == 0]
print(evens) # [0, 2, 4, 6, 8]
 
names = ["ada", "bo", "cy"]
capitalized = {name: name.title() for name in names}
print(capitalized)
output
[0, 1, 4, 9, 16, 25]
[0, 2, 4, 6, 8]
{'ada': 'Ada', 'bo': 'Bo', 'cy': 'Cy'}
14

Classes & OOP

A class is a blueprint for creating objects that bundle data (attributes) with behavior (methods). __init__ runs automatically when a new instance is created, and self refers to the specific instance a method is being called on.

  • Every instance method's first parameter is self by convention.
  • Attributes set in __init__ (like self.name) belong to that specific object.
  • __str__ controls what print(instance) actually displays.
classes-oop.py
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
 
def bark(self):
return f"{self.name} says woof!"
 
def __str__(self):
return f"Dog({self.name}, {self.breed})"
 
rex = Dog("Rex", "Labrador")
print(rex.bark()) # Rex says woof!
print(rex) # Dog(Rex, Labrador)
output
Rex says woof!
Dog(Rex, Labrador)
15

Inheritance

Inheritance lets a class reuse and extend another class's behavior. The child class inherits every attribute and method from the parent, and can override any of them or add new ones of its own.

  • Syntax: class Child(Parent):.
  • super().__init__(...) calls the parent's constructor from the child.
  • isinstance(obj, ParentClass) is True for instances of any subclass too.
inheritance.py
class Animal:
def __init__(self, name):
self.name = name
 
def speak(self):
return f"{self.name} makes a sound"
 
class Cat(Animal):
def speak(self): # override
return f"{self.name} says meow"
 
class Puppy(Animal):
def __init__(self, name, age):
super().__init__(name)
self.age = age
 
whiskers = Cat("Whiskers")
print(whiskers.speak()) # Whiskers says meow
print(isinstance(whiskers, Animal)) # True
output
Whiskers says meow
True
16

Exception Handling

try/except lets your program recover from an error instead of crashing outright. Python raises a specific exception type for each kind of failure, and you can catch that exact type — or several — to handle it gracefully.

  • else runs only if the try block succeeded; finally always runs, error or not.
  • Catch specific exceptions (ValueError, KeyError) rather than a bare except when possible.
  • raise lets you trigger your own exception, including custom ones.
exceptions.py
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Can't divide by zero!")
return None
else:
print("Division succeeded")
return result
finally:
print("Done attempting division")
 
print(divide(10, 2))
print(divide(10, 0))
output
Division succeeded
Done attempting division
5.0
Can't divide by zero!
Done attempting division
None
17

File Handling

open() is how Python reads and writes files. Using it with the with statement is the standard practice — it guarantees the file is closed automatically once the block ends, even if an error happens inside it.

  • Modes: 'r' read, 'w' write (overwrites), 'a' append, 'x' create-only.
  • with open(...) as f: closes the file for you — no manual f.close() needed.
  • .readlines() and iterating the file object both give you line-by-line access.
file-handling.py
# Writing to a file
with open("notes.txt", "w") as f:
f.write("Learning Python with CodeNFacts\n")
f.write("File handling is straightforward\n")
 
# Reading it back
with open("notes.txt", "r") as f:
for line in f:
print(line.strip())
output
Learning Python with CodeNFacts
File handling is straightforward
18

Modules & Imports

A module is just a .py file, and import is how you pull functions, classes, or variables from one file into another. Python also ships a large standard library of modules for common tasks like math, dates, and randomness.

  • import module then module.function() — or from module import function to call it directly.
  • as gives an imported module a shorter alias, like import numpy as np.
  • if __name__ == "__main__": guards code that should only run when the file is executed directly.
modules.py
import math
from datetime import date
import random as rnd
 
print(math.sqrt(16)) # 4.0
print(date.today().year) # e.g. 2026
print(rnd.choice(["a", "b", "c"]))
 
# my_module.py
def helper():
return "I'm reusable!"
 
if __name__ == "__main__":
print(helper())
output
4.0
2026
b
I'm reusable!
19

Decorators

A decorator is a function that wraps another function to add behavior — like logging or timing — without changing the original function's code. The @decorator_name syntax above a function is just shorthand for passing that function into the decorator.

  • A decorator takes a function in and returns a new function out.
  • *args, **kwargs in the wrapper let it decorate any function, regardless of its signature.
  • functools.wraps preserves the original function's name and docstring (worth knowing, easy to skip at first).
decorators.py
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} finished")
return result
return wrapper
 
@log_call
def add(a, b):
return a + b
 
print(add(2, 3))
output
Calling add
add finished
5
20

Generators

A generator produces values one at a time, on demand, instead of building an entire list in memory up front. Any function using yield instead of return becomes a generator — it pauses at each yield and resumes right where it left off on the next call.

  • yield pauses the function and hands back a value; next() resumes it.
  • Generators are memory-efficient for large or infinite sequences.
  • A generator expression looks like a list comprehension with () instead of [].
generators.py
def countdown(n):
while n > 0:
yield n
n -= 1
 
for num in countdown(3):
print(num)
 
squares = (n * n for n in range(5)) # generator expression
print(next(squares)) # 0
print(next(squares)) # 1
print(list(squares)) # [4, 9, 16] — remaining values
output
3
2
1
0
1
[4, 9, 16]

Ready to go deeper?

Ask the AI tutor any Python question and get a step-by-step walkthrough, live.

Ask the AI tutorBrowse other categories
Thanks for downloading ! Happy learning ..❤️..