Python › Advanced Python

Classes, decorators, and generators

4 min read Advanced 4 sections

These features make your tools cleaner and able to handle scale — and you’ll meet them constantly reading other people’s security tools. You don’t need to master them to be productive, but recognising and using them marks the step from scripting to tool-building.

You'll learn to

  • Use a class to organise a stateful tool
  • Add reusable behaviour with a decorator
  • Stream huge inputs with a generator

Classes bundle state and behaviour

import requests

class Target:
    def __init__(self, base_url):       # constructor — runs at creation
        self.base = base_url
        self.session = requests.Session()
        self.findings = []

    def get(self, path):
        return self.session.get(self.base + path, timeout=10)

    def add_finding(self, title, detail):
        self.findings.append({"title": title, "detail": detail})

t = Target("https://example.com")
t.add_finding("IDOR", "/api/users/2 accessible as user 1")

A class is a template for objects that bundle data and behaviour together. __init__ is the constructor; self holds the object’s own data. Classes shine when you have a thing with state and operations — a Target with its session and findings — keeping larger tools organised instead of sprawling globals.

Decorators add behaviour without changing code

import functools, time

def retry(times=3):
    def wrap(fn):
        @functools.wraps(fn)
        def inner(*args, **kwargs):
            for attempt in range(times):
                try:
                    return fn(*args, **kwargs)
                except Exception:
                    if attempt == times - 1: raise
                    time.sleep(0.5)
        return inner
    return wrap

@retry(times=3)            # fetch now auto-retries 3 times
def fetch(url):
    return requests.get(url, timeout=5).text

A decorator (the @name above a function) wraps that function to add behaviour without changing its body — here, automatic retries. You’ll see decorators everywhere in frameworks and tools.

Generators stream without exhausting memory

def read_targets(path):
    with open(path) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                yield line          # produce one at a time, constant memory

for target in read_targets("million_hosts.txt"):   # never holds all in RAM
    pass

A generator (a function using yield) produces values one at a time instead of building a giant list, so you can stream a million-line target file in constant memory — essential for large-scale recon.

Checkpoint

Why use a generator (yield) to read a million-line target file instead of reading it all into a list?

Try it yourself

Write a generator that reads a file and yields only the non-empty, non-comment lines one at a time. Then write a simple decorator that prints how long the decorated function took to run, and apply it to a function. Notice how each adds capability without changing the core logic.

Key takeaways

  • Classes bundle state and behaviour — good for stateful tools.
  • Decorators add behaviour (retry, logging, timing) without changing a function.
  • Generators (yield) stream large inputs in constant memory.
  • These let you read any security tool and scale your own.

Quick quiz

Finally, how professionals weave all of this into their daily security work.

Was this lesson helpful?