Python › Advanced Python
Classes, decorators, and generators
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?
A generator produces one line at a time and never holds the whole file in memory, so memory use stays constant no matter how large the file. Reading a million lines into a list would consume large amounts of RAM. For large-scale recon over huge target lists, the generator lets the tool scale without exhausting memory.
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.