Modelling the entities a security tool works with — targets, ports, results, reporters — as classes, so the code stays organised as it grows from a script into a tool.
Part of the [[Readme|Python for Security Professionals]] course.
Object-Oriented Programming (OOP) bundles data (attributes) and the behaviour that acts on that data (methods) into a single unit called an object. A class is the blueprint; an object (or instance) is a concrete thing built from that blueprint.
In Python everything is an object — strings, lists, sockets, and file handles all expose attributes and .method() calls. Writing your own classes lets you model the entities a security tool deals with so that state and the operations on it travel together instead of being threaded through every function call.
This module builds the four classic pillars one at a time — Encapsulation, Inheritance, Abstraction, and Polymorphism — and finishes with the dunder methods that hook your objects into Python's own syntax.
[!note] Module hub OOP is one paradigm, not a requirement. Small scripts are often clearer as plain functions — see [[Methods-and-Functions/Readme|Methods & Functions]]. Reach for classes when you have state and behaviour that travel together, or many objects of the same shape.
By the end of this module you will be able to:
- Define classes, construct instances, and initialise state in
__init__. - Distinguish class attributes from instance attributes and avoid the shared-mutable-default bug.
- Choose correctly between instance, class, and static methods.
- Build a shallow inheritance hierarchy and extend behaviour with
super(). - Encapsulate state behind validated properties, and explain why
__privateis not a security control. - Design a plugin interface using duck typing or an abstract base class.
- Implement the dunder methods that make objects printable, comparable, and usable in
withblocks.
- [[Methods-and-Functions/Readme|Methods & Functions]] — functions, arguments, return values, and scope.
- [[Python-Objects-and-Data-Structure-Basics/Readme|Python Objects & Data Structure Basics]] — lists, dictionaries, and mutability.
- [[Python-Statements-and-Control-Flow/Readme|Python Statements & Control Flow]] — loops and conditionals.
Ordered so each topic builds on the last:
- [[Understanding-Objects]] — what an object is, and why everything in Python is one.
- [[Defining-Classes]] — the
classkeyword,__init__, and cheap, side-effect-free construction. - [[Class-Attributes]] — shared defaults, attribute lookup order, and the mutable-class-attribute trap.
- [[Instance-Attributes]] — per-object state,
__dict__, and declaring every attribute in__init__. - [[Instance-Methods]] — the default method flavour and where a class's invariants live.
- [[Class-Methods]] —
@classmethod, alternate constructors, and why factories must returncls(...). - [[Static-Methods]] —
@staticmethodhelpers, and when a plain module is the better answer. - [[Inheritance]] — the is-a relationship, the MRO, and keeping hierarchies shallow.
- [[Method-Overriding]] — replacing versus extending inherited behaviour.
- [[Super-Function]] — cooperative delegation along the MRO, including with mixins.
- [[Encapsulation]] — underscore conventions, name mangling, and validated properties.
- [[Polymorphism]] — duck typing, abstract base classes, and plugin interfaces.
- [[Magic-Dunder-Methods]] —
__str__,__repr__,__eq__,__len__, and the context-manager protocol.
Two closely related topics are covered in [[Advanced-Python-Data-Structures/Readme|Advanced Python Data Structures]] rather than repeated here:
- [[Advanced-Python-Data-Structures/Data-Classes|Data Classes]] —
@dataclassgenerates__init__,__repr__, and__eq__for data-holder classes. - [[Advanced-Python-Data-Structures/Property-Decorators|Property Decorators]] —
@propertygetters, setters, and computed attributes in depth.
Work through these in order; each builds on the previous one.
- Model a target. Write a
Targetclass holdingip,hostname, andopen_ports, validating the IP in__init__. - Add behaviour. Give it
record(port)that ignores duplicates andis_web_host()that checks for 80/443/8080. - Add a factory. Write a
from_cidr()class method returning a list ofTargetobjects for a/29lab range. - Build the hierarchy. Create a
BaseScannerwith arun()that performs a scope check then calls an overridableprobe(); subclass it asTCPScanner. - Make it printable. Add
__repr__and__eq__so results de-duplicate correctly in aset(). - Make it pluggable. Define a
ScanPlugin(ABC)and implement two plugins the engine drives polymorphically. - Make it safe. Add a context manager that guarantees every socket is closed even when a probe raises.
The recurring shape of a Python security tool maps directly onto classes:
Scanner drives the engagement, owns configuration and the plugin list
├── Target one host: ip, hostname, os_guess
├── Port a validated port number, with is_privileged
├── Result what a probe found: port, state, banner, evidence
└── Reporter renders results as text, JSON, or Markdown
A minimal, complete version:
#!/usr/bin/env python3
"""Class-based skeleton for a small, authorized-use port scanner."""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class Port:
"""A validated TCP/UDP port number."""
def __init__(self, number):
if not 0 < number <= 65535:
raise ValueError(f"invalid port: {number}")
self.number = number
@property
def is_privileged(self):
return self.number < 1024
def __repr__(self):
return f"Port({self.number})"
@dataclass
class Result:
"""What a single probe found."""
port: int
state: str
banner: str = ""
@dataclass
class Target:
"""One host in scope for this engagement."""
ip: str
hostname: str = "unknown"
results: list = field(default_factory=list)
def record(self, result):
self.results.append(result)
@property
def open_ports(self):
return [r.port for r in self.results if r.state == "open"]
class Probe(ABC):
"""Interface every scan plugin must implement."""
@abstractmethod
def name(self):
...
@abstractmethod
def run(self, target, port):
...
class ConnectProbe(Probe):
"""Stand-in for a real TCP connect probe."""
def name(self):
return "tcp-connect"
def run(self, target, port):
state = "open" if port.number in (22, 80) else "closed"
return Result(port=port.number, state=state)
class Reporter:
"""Renders results. Subclass to add a format."""
def render(self, target):
ports = ", ".join(str(p) for p in sorted(target.open_ports))
return f"{target.ip}: {ports or 'no open ports'}"
class Scanner:
"""Drives probes across a target's ports."""
def __init__(self, probes, reporter=None):
self.probes = probes
self.reporter = reporter or Reporter()
def scan(self, target, ports):
for port in ports:
for probe in self.probes: # polymorphic dispatch
target.record(probe.run(target, port))
return self.reporter.render(target)
def main():
target = Target("127.0.0.1", hostname="localhost")
ports = [Port(n) for n in (22, 80, 443)]
scanner = Scanner(probes=[ConnectProbe()])
print(scanner.scan(target, ports))
if __name__ == "__main__":
main()127.0.0.1: 22, 80
Every pillar is visible here: Port encapsulates validation, Probe defines an abstract interface, ConnectProbe supplies the polymorphic implementation, and Reporter is designed to be inherited from. [[Security-Tool-Development/Security-Tool-Architecture|Security Tool Architecture]] takes the same design and turns it into an installable package.
[!warning] Authorized use only The skeleton above targets
127.0.0.1. Only scan systems you own or are explicitly authorized to test.
- Mutable class attributes used as per-object state — one list shared by every instance, which in a scanner means one host's findings appearing under another's.
- Skipping
super().__init__()in a subclass, leaving parent attributes unset. - Treating
__privateas a security control — name mangling only prevents accidental collisions. - Deep inheritance towers where composition, a mixin, or a plain function would be clearer.
- Overriding
__eq__without__hash__, making instances unusable in sets and dict keys. - Doing network or file I/O inside
__init__, making objects slow to build and impossible to test offline. - Leaking secrets through a default
__repr__into logs and tracebacks.
- Keep
__init__cheap and side-effect free; do I/O in explicit methods. - Define
__repr__on every class you will debug — it makes logs and tracebacks readable. - Prefer composition ("has-a") over deep inheritance ("is-a") chains.
- Use
@dataclassfor data-holder classes andabc.ABCto pin down plugin interfaces. - Initialise mutable state inside
__init__or withdefault_factory, never as a bare class attribute. - Call
super().__init__(...)in every subclass constructor. - Favour
@propertyoverget_x()/set_x()for validated attribute access.
- What is the difference between a class attribute and an instance attribute, and how does attribute lookup resolve between them?
- When should you use a
@classmethodrather than a@staticmethod? - What security issue can occur if a mutable class attribute is used to collect scan results across multiple targets?
- Why is
__privatenot a security control, and what should you use instead for real secrets? - How would you troubleshoot an
AttributeErrorraised inside an inherited method? - What is the difference between
__str__and__repr__, and which should you define first? - Write a Python example that demonstrates polymorphic dispatch across two classes with no shared base class.
# Run the scanner skeleton from this page
python3 scanner_skeleton.py
# Inspect a class interactively
python3 -c "import ipaddress; print(type(ipaddress.ip_address('127.0.0.1')).__mro__)"
# List the attributes and methods an object exposes
python3 -c "from pathlib import Path; print([n for n in dir(Path('.')) if not n.startswith('_')])"
# Show a class's method resolution order
python3 -c "from ipaddress import IPv4Address; print(IPv4Address.__mro__)"- Python docs — Classes tutorial
- Python docs — Data model (special method names)
- Python docs —
abc(Abstract Base Classes) - Python docs —
dataclasses - Python docs — The Python 2.3 Method Resolution Order
- PEP 8 — Style Guide for Python Code
- Real Python — Object-Oriented Programming in Python 3
- [[Methods-and-Functions/Readme|Methods & Functions]] — functions vs methods, the building blocks of classes
- [[Error-and-Exception-Handling/Readme|Error & Exception Handling]] — raising and handling errors in constructors and setters
- [[Advanced-Python-Data-Structures/Readme|Advanced Python Data Structures]] — dataclasses, properties, generators
- [[Advanced-Python-Modules/Readme|Advanced Python Modules]] —
collections,abc, and other stdlib modules used here - [[Security-Tool-Development/Readme|Security Tool Development]] — applying these classes to real tooling
- [[Readme|Python for Security Professionals]] — course home
- Previous: [[Methods-and-Functions/Readme|5. Methods & Functions]]
- Home: [[Readme|Course Home]]
- Next: [[Input-Output-File-Handling/Readme|7. Input/Output File Handling]]