Mathew K Analytics

Lesson 2 · Python for Data Analysts

Python Zero to Hero: The Complete Beginner-to-Advanced Course

One notebook, start to finish: from your very first print() to decorators, generators, and object-oriented programming. No prior experience needed. Let's…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Python Zero to Hero: The Complete Beginner-to-Advanced Course#

  • One notebook, start to finish: from your very first print() to decorators, generators, and object-oriented programming.
  • No prior experience needed. Let's get straight into it.

Before You Start#

  • Install Python 3 from python.org (on Windows, check 'Add python.exe to PATH'), then install VS Code from code.visualstudio.com with its Python and Jupyter extensions.
  • Open the Command Palette (Control/Command plus Shift plus P), run 'Create: New Jupyter Notebook', and select your Python interpreter as the kernel.

Part 1: Fundamentals#

Your First Cell: print()#

print('Hello, world!')
print(42)
Hello, world!
42

Data Types: Strings, Integers, Floats, Booleans#

  • Text values are strings, written with single or double quotes.
  • Whole numbers are integers; numbers with a decimal point are floats.
  • True/False values are booleans, always capitalized in Python.
a_string = 'Python is fun'
an_integer = 25
a_float = 3.14
a_boolean = True
print(type(a_string), type(an_integer), type(a_float), type(a_boolean))
<class 'str'> <class 'int'> <class 'float'> <class 'bool'>

Variables and Naming#

student_name = 'Jordan'
student_age = 19
print(student_name, student_age)
student_age = 20
print(student_name, student_age)
Jordan 19
Jordan 20

f-Strings: Combining Text and Values#

price = 19.99
quantity = 3
total = price * quantity
print(f'{quantity} items at ${price} each comes to ${total:.2f}')
3 items at $19.99 each comes to $59.97

Arithmetic and Operators#

print(7 + 3)
print(7 - 3)
print(7 * 3)
print(7 / 3)
print(7 // 3)
print(7 % 3)
print(7 ** 2)
10
4
21
2.3333333333333335
2
1
49

Type Conversion (Casting)#

age_text = '25'
age_number = int(age_text)
print(age_number + 1)
print(str(age_number) + ' years old')
26
25 years old

Getting Input From the User#

name = input('What is your name? ')
print(f'Nice to meet you, {name}!')
Nice to meet you, Priya!

Conditionals: if, elif, else#

  • if, elif, and else let a program branch: run one block of code when a condition is true, and a different block otherwise.
score = 82
if score >= 90:
    grade = 'A'
elif score >= 80:
    grade = 'B'
elif score >= 70:
    grade = 'C'
else:
    grade = 'F'
print(f'Score {score} is a grade {grade}')
Score 82 is a grade B

Comparison and Logical Operators#

age = 25
has_ticket = True
print(age >= 18 and has_ticket)
print(age < 18 or not has_ticket)
print(age == 25)
print(age != 30)
True
False
True
True

Part 2: Data Structures#

Lists: Ordered, Changeable Collections#

fruits = ['apple', 'banana', 'cherry']
print(fruits[0])
print(fruits[-1])
fruits.append('date')
fruits.remove('banana')
print(fruits)
print(len(fruits))
apple
cherry
['apple', 'cherry', 'date']
3
numbers = [5, 2, 9, 1, 7]
print(numbers[1:4])
numbers.sort()
print(numbers)
numbers.sort(reverse=True)
print(numbers)
[2, 9, 1]
[1, 2, 5, 7, 9]
[9, 7, 5, 2, 1]

Tuples: Ordered, Unchangeable Collections#

point = (3, 7)
print(point[0], point[1])
x, y = point
print(f'x is {x}, y is {y}')
3 7
x is 3, y is 7

Sets: Unique, Unordered Values#

tags = {'python', 'coding', 'python', 'tutorial'}
print(tags)
tags.add('beginner')
print('python' in tags)
{'tutorial', 'coding', 'python'}
True

Dictionaries: Key-Value Pairs#

person = {'name': 'Alex', 'age': 30, 'city': 'Austin'}
print(person['name'])
person['age'] = 31
person['job'] = 'Engineer'
print(person)
for key, value in person.items():
    print(f'{key}: {value}')
Alex
{'name': 'Alex', 'age': 31, 'city': 'Austin', 'job': 'Engineer'}
name: Alex
age: 31
city: Austin
job: Engineer

Comprehensions: Building Collections in One Line#

squares = [n ** 2 for n in range(1, 6)]
print(squares)
evens = [n for n in range(1, 11) if n % 2 == 0]
print(evens)
[1, 4, 9, 16, 25]
[2, 4, 6, 8, 10]
words = ['python', 'is', 'fun']
word_lengths = {word: len(word) for word in words}
print(word_lengths)
{'python': 6, 'is': 2, 'fun': 3}

Part 3: Loops#

for Loops and range()#

for number in range(5):
    print(number)
for fruit in ['apple', 'banana', 'cherry']:
    print(fruit.upper())
0
1
2
3
4
APPLE
BANANA
CHERRY

while Loops, break, and continue#

count = 0
while count < 10:
    count += 1
    if count == 3:
        continue
    if count == 7:
        break
    print(count)
1
2
4
5
6

Part 4: Functions#

Defining and Calling Functions#

def greet(name):
    """Return a friendly greeting for the given name."""
    return f'Hello, {name}!'

print(greet('Sam'))
Hello, Sam!

Default Parameters and Keyword Arguments#

def make_coffee(size='medium', extra_shot=False):
    shot_text = ' with an extra shot' if extra_shot else ''
    return f'One {size} coffee{shot_text}, coming up!'

print(make_coffee())
print(make_coffee('large'))
print(make_coffee(extra_shot=True, size='small'))
One medium coffee, coming up!
One large coffee, coming up!
One small coffee with an extra shot, coming up!

*args and **kwargs: Flexible Argument Counts#

def total_cost(*prices, **fees):
    subtotal = sum(prices)
    extra = sum(fees.values())
    return subtotal + extra

print(total_cost(10, 20, 30))
print(total_cost(10, 20, tax=5, shipping=8))
60
43

Lambda Functions#

square = lambda n: n ** 2
print(square(6))
people = [('Sam', 34), ('Ana', 22), ('Kai', 45)]
people.sort(key=lambda person: person[1])
print(people)
36
[('Ana', 22), ('Sam', 34), ('Kai', 45)]

Variable Scope#

counter = 0

def increment():
    global counter
    counter += 1

increment()
increment()
print(counter)
2

Part 5: Error Handling#

try, except, else, finally#

def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print('Cannot divide by zero.')
        return None
    else:
        print('Division succeeded.')
        return result
    finally:
        print('Division attempt finished.')

print(safe_divide(10, 2))
print(safe_divide(10, 0))
Division succeeded.
Division attempt finished.
5.0
Cannot divide by zero.
Division attempt finished.
None

Catching Multiple Exception Types#

def get_item(items, index):
    try:
        return items[index]
    except IndexError:
        return 'That index does not exist.'
    except TypeError:
        return 'The index must be a whole number.'

fruits = ['apple', 'banana']
print(get_item(fruits, 5))
print(get_item(fruits, 'first'))
That index does not exist.
The index must be a whole number.

Custom Exceptions#

class InsufficientFundsError(Exception):
    """Raised when a withdrawal exceeds the available balance."""
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(f'Cannot withdraw {amount}, balance is only {balance}')
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFundsError as e:
    print(f'Transaction failed: {e}')
Transaction failed: Cannot withdraw 250, balance is only 100

Part 6: Working With Files#

Reading and Writing Text Files#

with open('notes.txt', 'w') as f:
    f.write('Line one\n')
    f.write('Line two\n')
print('File written.')
File written.
with open('notes.txt', 'r') as f:
    contents = f.read()
print(contents)

with open('notes.txt', 'r') as f:
    for line in f:
        print('Line:', line.strip())
Line one
Line two

Line: Line one
Line: Line two

Writing Your Own Context Manager#

class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        import time
        elapsed = time.time() - self.start
        print(f'Elapsed: {elapsed:.4f} seconds')

with Timer():
    total = sum(n * n for n in range(1_000_000))
print(total)
Elapsed: 0.0668 seconds
333332833333500000

Part 7: Modules, Packages, and Environments#

Modules and the Standard Library#

  • Every Python file is a module. import brings functions, variables, and classes from one module into another.
  • Python's standard library ships dozens of built-in modules for common tasks, with nothing extra to install.
import math
import random
import datetime

print(math.sqrt(81))
print(random.choice(['heads', 'tails']))
print(datetime.date.today())
9.0
heads
2026-08-15

Packages, pip, and Virtual Environments#

  • External packages live on PyPI and install with pip: 'pip install package_name'.
  • A virtual environment is an isolated set of installed packages for one project, so different projects can use different, even conflicting, package versions without interfering with each other.
  • Create one with 'python -m venv venv', then activate it before installing anything: 'venv\Scripts\activate' on Windows, or 'source venv/bin/activate' on macOS/Linux.

Part 8: Regular Expressions#

Pattern Matching With re#

import re

text = 'Contact us at support@example.com or sales@example.com'
emails = re.findall(r'[\w.]+@[\w.]+', text)
print(emails)
['support@example.com', 'sales@example.com']
sentence = 'There are 12 apples and 5 oranges'
numbers = re.findall(r'\d+', sentence)
print(numbers)

cleaned = re.sub(r'\d+', '#', sentence)
print(cleaned)
['12', '5']
There are # apples and # oranges

Part 9: Iterators and Generators#

Generator Functions With yield#

def countdown(start):
    while start > 0:
        yield start
        start -= 1
    yield 'Liftoff!'

for value in countdown(3):
    print(value)
3
2
1
Liftoff!
squares_gen = (n ** 2 for n in range(1_000_000))
print(type(squares_gen))
print(next(squares_gen))
print(next(squares_gen))
<class 'generator'>
0
1

Part 10: Decorators#

Functions as First-Class Objects#

def shout(text):
    return text.upper() + '!'

def apply_twice(func, value):
    return func(func(value))

print(apply_twice(shout, 'hello'))
HELLO!!

Writing a Decorator#

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f'Calling {func.__name__} with {args}')
        result = func(*args, **kwargs)
        print(f'{func.__name__} returned {result}')
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(3, 4)
Calling add with (3, 4)
add returned 7
7

Part 11: Object-Oriented Programming#

Classes and Objects#

class Animal:
    def __init__(self, name, sound):
        self.name = name
        self.sound = sound

    def make_sound(self):
        return f'{self.name} says {self.sound}'

generic_animal = Animal('Creature', '...')
print(generic_animal.make_sound())
Creature says ...

Inheritance and Polymorphism#

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name, 'Woof')

    def fetch(self):
        return f'{self.name} fetches the ball!'

class Cat(Animal):
    def __init__(self, name):
        super().__init__(name, 'Meow')

dog = Dog('Rex')
cat = Cat('Whiskers')
for animal in [dog, cat]:
    print(animal.make_sound())
Rex says Woof
Whiskers says Meow

The str Method#

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f'Point({self.x}, {self.y})'

p = Point(3, 4)
print(p)
Point(3, 4)

Part 12: Basic Unit Testing#

Writing Tests With unittest#

  • A unit test checks that one small piece of code behaves the way you expect, automatically, so you find out immediately if a change breaks something.
  • Python's built-in unittest module provides a standard structure for writing and running tests.
import unittest

def add(a, b):
    return a + b

class TestAdd(unittest.TestCase):
    def test_positive_numbers(self):
        self.assertEqual(add(2, 3), 5)

    def test_negative_numbers(self):
        self.assertEqual(add(-1, -1), -2)

runner = unittest.TextTestRunner(verbosity=0)
runner.run(unittest.TestLoader().loadTestsFromTestCase(TestAdd))
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
<unittest.runner.TextTestResult run=2 errors=0 failures=0>

Capstone Project: Word Frequency Analyzer#

  • Let's build one real program that pulls together nearly everything from this lesson: file I/O, regex, comprehensions, a custom exception, a decorator, and a class.
  • It reads a text file and reports its most frequently used words.
sample_text = (
    'Python is fun. Python is powerful. '
    'Learning Python takes practice, but Python rewards practice.'
)
with open('sample.txt', 'w') as f:
    f.write(sample_text)
print('sample.txt created.')
sample.txt created.
class EmptyFileError(Exception):
    """Raised when a file has no readable words in it."""
    pass
def timed(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f'{func.__name__} took {elapsed:.5f} seconds')
        return result
    return wrapper
class WordFrequencyAnalyzer:
    def __init__(self, filepath):
        self.filepath = filepath

    def load_words(self):
        with open(self.filepath, 'r') as f:
            text = f.read()
        words = re.findall(r"[a-z']+", text.lower())
        if not words:
            raise EmptyFileError(f'{self.filepath} has no readable words.')
        return words

    @timed
    def top_words(self, n=3):
        words = self.load_words()
        counts = {word: words.count(word) for word in set(words)}
        ranked = sorted(counts.items(), key=lambda pair: pair[1], reverse=True)
        return ranked[:n]
analyzer = WordFrequencyAnalyzer('sample.txt')
print(analyzer.top_words(3))
top_words took 0.00113 seconds
[('python', 4), ('is', 2), ('practice', 2)]
with open('empty.txt', 'w') as f:
    pass

empty_analyzer = WordFrequencyAnalyzer('empty.txt')
try:
    empty_analyzer.top_words()
except EmptyFileError as e:
    print(f'Analysis failed: {e}')
Analysis failed: empty.txt has no readable words.

Wrap-Up: What You Learned#

  • Fundamentals: variables, data types, f-strings, operators, casting, user input, conditionals, and boolean logic.
  • Data structures: lists, tuples, sets, dictionaries, and comprehensions.
  • Loops: for, while, range, break, and continue.
  • Functions: parameters, defaults, keyword arguments, *args/**kwargs, lambdas, and scope.
  • Error handling: try/except/else/finally and custom exceptions.
  • Files and context managers, including writing your own with enter and exit.
  • Modules, packages, pip, and virtual environments.
  • Regular expressions with the re module.
  • Iterators and generators with yield.
  • Decorators, and functions as first-class objects.
  • Object-oriented programming: classes, inheritance, polymorphism, and dunder methods.
  • Basic unit testing with unittest.
  • A capstone project combining nearly all of it into one working program.
  • You just went from print('Hello, world!') to writing decorators, generators, and tested, exception-safe classes, in one sitting. That's genuinely rare. If you want the next build to land in your feed automatically, subscribing is the move see you in the next one.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.