If you already know another language, half of Python will feel familiar. The other half will feel weird — until it clicks and you realize it’s actually better.
This lesson covers everything you need to write confident Python from day one.
Variables and Types
Python is dynamically typed (no type declarations) but strongly typed (no implicit coercion).
# No type declarations needed
name = "Alice"
age = 30
pi = 3.14159
is_active = True
# But types are enforced — no implicit coercion
"age: " + 30 # TypeError! (unlike JavaScript)
"age: " + str(30) # "age: 30" — explicit conversion required
# Check types at runtime
type(name) # <class 'str'>
type(age) # <class 'int'>
isinstance(age, int) # True
isinstance(age, (int, float)) # True — checks multiple typesType Hints (Optional but Recommended)
Python 3.10+ has excellent type hint support. They don’t enforce anything at runtime — they’re for tooling (mypy, IDEs).
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
# Complex types
from typing import Optional
def find_user(user_id: int) -> Optional[dict]:
"""Returns user dict or None if not found."""
...
# Python 3.10+ union syntax
def process(value: int | str) -> str:
return str(value)
# Collections
def get_scores() -> list[int]:
return [95, 87, 92]
def get_config() -> dict[str, str | int]:
return {"host": "localhost", "port": 8080}Numbers
# Integers have arbitrary precision (no overflow!)
big = 10 ** 100 # This just works. No BigInt needed.
# Underscores for readability
population = 8_000_000_000
hex_color = 0xFF_AA_00
# Division gotcha: / always returns float, // returns int
10 / 3 # 3.3333... (float division)
10 // 3 # 3 (floor division)
10 % 3 # 1 (modulo)
# Power
2 ** 10 # 1024 (not ^ like some languages)
# Truthiness
bool(0) # False
bool(42) # True
bool("") # False
bool("hello") # True
bool([]) # False
bool([1]) # True
bool(None) # FalseStrings
# f-strings (Python 3.6+) — the only string formatting you need
name = "Alice"
age = 30
f"Name: {name}, Age: {age}" # "Name: Alice, Age: 30"
f"Next year: {age + 1}" # "Next year: 31"
f"Price: ${19.99:.2f}" # "Price: $19.99"
f"{'hello':>20}" # " hello" (right-align)
f"{name!r}" # "'Alice'" (repr)
# Multiline strings
query = """
SELECT *
FROM users
WHERE active = true
"""
# Strings are immutable
s = "hello"
# s[0] = "H" # TypeError!
s = "H" + s[1:] # Create a new string instead
# Useful string methods
"hello world".split() # ["hello", "world"]
", ".join(["a", "b", "c"]) # "a, b, c"
" hello ".strip() # "hello"
"hello".startswith("hel") # True
"hello world".replace("world", "python") # "hello python"
"hello".upper() # "HELLO"
"Hello".lower() # "hello"
"hello world".title() # "Hello World"
"abc123".isalnum() # TrueLists (Mutable Arrays)
# Create
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14] # Any types
# Access
nums[0] # 1
nums[-1] # 5 (last element)
nums[-2] # 4 (second to last)
# Slicing — [start:stop:step] (stop is exclusive)
nums[1:3] # [2, 3]
nums[:3] # [1, 2, 3] (first 3)
nums[2:] # [3, 4, 5] (from index 2)
nums[::2] # [1, 3, 5] (every 2nd)
nums[::-1] # [5, 4, 3, 2, 1] (reversed)
# Modify
nums.append(6) # [1, 2, 3, 4, 5, 6]
nums.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]
nums.extend([7, 8]) # [0, 1, 2, 3, 4, 5, 6, 7, 8]
nums.pop() # removes and returns 8
nums.remove(0) # removes first occurrence of 0
del nums[1] # removes by index
# Unpacking
first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]
first, *middle, last = [1, 2, 3, 4, 5]
# first = 1, middle = [2, 3, 4], last = 5Tuples (Immutable Lists)
# Create
point = (3, 4)
single = (42,) # Note the comma — (42) is just 42 in parentheses
rgb = 255, 128, 0 # Parentheses are optional
# Access (same as list)
point[0] # 3
point[-1] # 4
# But can't modify
# point[0] = 5 # TypeError!
# Tuple unpacking (extremely common in Python)
x, y = point # x = 3, y = 4
# Swap without temp variable
a, b = 1, 2
a, b = b, a # a = 2, b = 1
# Named tuples — lightweight classes
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x # 3
p.y # 4Dictionaries (Hash Maps)
# Create
user = {"name": "Alice", "age": 30, "active": True}
# Access
user["name"] # "Alice"
user.get("email") # None (no KeyError)
user.get("email", "") # "" (default value)
# Modify
user["email"] = "[email protected]"
user.update({"age": 31, "city": "NYC"})
# Remove
del user["active"]
email = user.pop("email", None) # Remove and return
# Iterate
for key in user:
print(key, user[key])
for key, value in user.items():
print(f"{key}: {value}")
# Check membership
"name" in user # True
"phone" in user # False
# Merge (Python 3.9+)
defaults = {"theme": "dark", "lang": "en"}
overrides = {"lang": "fr"}
config = defaults | overrides # {"theme": "dark", "lang": "fr"}
# Dict comprehension
squares = {n: n**2 for n in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}Sets
# Create
tags = {"python", "coding", "tutorial"}
empty_set = set() # NOT {} — that's an empty dict!
# Operations
tags.add("beginner")
tags.discard("coding") # No error if missing (unlike .remove())
# Set operations — blazing fast
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b # {1, 2, 3, 4, 5, 6} — union
a & b # {3, 4} — intersection
a - b # {1, 2} — difference
a ^ b # {1, 2, 5, 6} — symmetric difference
# Membership test is O(1) — use sets for fast lookups
valid_ids = {1001, 1002, 1003, 1004}
1002 in valid_ids # True (instant, unlike list scan)Comprehensions
This is Python’s killer feature. Master comprehensions and you’ll write less code than any other language.
# List comprehension
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# With condition
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Transform + filter
names = ["alice", "bob", "charlie", "dave"]
long_names = [name.title() for name in names if len(name) > 3]
# ["Alice", "Charlie", "Dave"]
# Dict comprehension
word_lengths = {word: len(word) for word in names}
# {"alice": 5, "bob": 3, "charlie": 7, "dave": 4}
# Set comprehension
unique_lengths = {len(word) for word in names}
# {3, 4, 5, 7}
# Nested comprehension (flatten)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Generator expression (lazy — doesn't create list in memory)
total = sum(x**2 for x in range(1_000_000))
# Computes without creating a million-element listGotchas Coming From Other Languages
Mutable Default Arguments
# BUG: Default list is shared across all calls!
def append_to(item, target=[]):
target.append(item)
return target
append_to(1) # [1]
append_to(2) # [1, 2] — WAT? Expected [2]!
# FIX: Use None as default
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return targetis vs ==
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — same value
a is b # False — different objects
# `is` checks identity (same object in memory)
# `==` checks equality (same value)
# ONLY use `is` for None checks:
if x is None: # correct
if x == None: # works but unpythonicEverything Is a Reference
a = [1, 2, 3]
b = a # b points to the SAME list
b.append(4)
print(a) # [1, 2, 3, 4] — a changed too!
# To copy:
b = a.copy() # Shallow copy
b = list(a) # Shallow copy
import copy
b = copy.deepcopy(a) # Deep copy (nested objects too)Key Takeaways
- Python is dynamically typed but strongly enforced — no silent coercion.
- Everything is an object.
int,str, even functions have methods. - Lists are mutable, tuples are immutable. Use tuples for fixed data.
- Dicts and sets use hash tables — O(1) lookups.
- Comprehensions replace 90% of the loops you’d write in other languages.
- Watch out for mutable default arguments and reference semantics.
- Use
isforNone,==for everything else.
