What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Python

Step 52. Classes and Object Orientation — Data and Behavior in One Box

Step 52Estimated practice · 3 hours

Level 1 — Programming and the Computer’s Insides | Difficulty ★★☆☆☆ | Estimated time: 3 hours

Prerequisites: Steps 41–51 complete. You know functions (Step 44) and dictionaries (Step 42).

  • What you need: a PC with Python installed, a text editor, a terminal.
  • Caution: today’s practice is 100% safe. Nothing connects to the network or touches the system — it’s all Python code practice inside your own computer.

In Step 33, we once moved the computers on a network into dictionaries:

pc1 = {"name": "Desktop", "ip": "192.168.0.10", "on": True}
pc2 = {"name": "Laptop", "ip": "192.168.0.20", "on": False}

This approach collapses once you have about twenty computers. Nobody warns you when you typo pc1["name"], and there’s no place to attach an action like "power on." It has data but no behavior.

Today’s topic, object-oriented programming, is the answer to this problem. It’s a way of thinking that bundles related data and behavior into one box: the box’s blueprint is the class, and the actual thing stamped out from the blueprint is the instance. It feels awkward at first. That’s normal. But this way of thinking is the common language of large programs and security tools — scanners, analysis tools, all of them are made of classes.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain the relationship between a class (the blueprint) and an instance (the thing)
  • Create attributes (data) and methods (behavior) with a clear distinction
  • Explain why __init__ and self are needed
  • Stamp out multiple instances from one class and check that each holds a different state
  • See self-related errors (TypeError, NameError, AttributeError) and fix them yourself

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3.12. Standard syntax only — nothing new to install or import
Today’s syntax class Name:, def __init__(self, ...), self.attribute, def method(self):, creating instances with Name(...)
Concepts needed Classes and instances, attributes and methods, self, special methods (init)

2-1. Blueprint and Thing — Class and Instance

Think of a pastry mold and the pastries. The class is the mold. There’s only one. The instances are the pastries stamped from that mold — you can make as many as you want, and each has a different filling (attribute values). From one Host class, you stamp out a router instance, a my-PC instance, a virtual machine instance.

2-2. Attributes and Methods — Data and Behavior

An attribute is the data a thing holds. A Host’s name, IP, power state. A method is an action the thing can perform — "power on," "print info." It’s the same as a function, except that it belongs inside a class, and you call it with a dot (.). Like pc1.boot().

2-3. self — The Pronoun "Myself"

Inside a method, self is the word that points to "this thing itself." Just as each pastry has a different filling, each instance has different attribute values, and for a method to point to "my filling" it needs a pronoun — that’s self. Python has a rule that you write self as a method’s first argument.

2-4. init — What It Does at Birth

A special method that runs automatically the moment an instance is created. Short for "initialize," this is where you receive the first attribute values and plant them. The two underscores on each side (__) are Python’s marker for special methods.


3. Follow Along

3-1. First Class — Drawing the Blueprint

Create host.py:

class Host:
    def __init__(self, name, ip):
        self.name = name
        self.ip = ip
        self.on = False

    def boot(self):
        self.on = True
        print(self.name, "powered on.")

    def info(self):
        state = "on" if self.on else "off"
        print(self.name, "|", self.ip, "|", state)

How to read the code: class Host: is the start of the blueprint, and the indented things are its contents. __init__ receives name and ip when an instance is made and plants them into self.name and self.ip. self.on = False is the initial setting meaning "the power is off by default."

Why: first see with your eyes what it looks like when data (name, ip, on) and behavior (boot, info) go into one box.

3-2. Stamping Out Instances

Continue writing in the same file:

pc1 = Host("Desktop", "192.168.0.10")
pc2 = Host("Laptop", "192.168.0.20")

pc1.info()
pc1.boot()
pc2.info()
pc1.info()
Desktop | 192.168.0.10 | off
Desktop powered on.
Laptop | 192.168.0.20 | off
Desktop | 192.168.0.10 | on

(Measured 2026-09-09.)

How to read the output: the single line Host("Desktop", "192.168.0.10") is the moment of "putting values in the mold and stamping." At that moment __init__ runs automatically. pc1.boot() powers on only pc1 — pc2 is still off. They came from the same mold, but their states are their own.

Predict: if you call pc2.info() again without pc2.boot(), what state will it show? Predict, then check.

Why: "each instance holds an independent state" is half of object orientation.

3-3. Confirming What self Is

Create self_test.py:

class Cat:
    def whoami(self):
        print("I am", self, ".")

a = Cat()
b = Cat()
a.whoami()
b.whoami()
I am <__main__.Cat object at 0x0000028E41F3F620> .
I am <__main__.Cat object at 0x0000028E41F3CCB0> .

(Measured 2026-09-09. The 0x… address differs every run.)

How to read the output: a’s and b’s self point to different addresses (0x…). It’s proof that self is "the very instance that made the call." Internally, Python runs a.whoami() like "Cat.whoami(a)", putting a into self. This delivery is exactly why you write self in a method.

3-4. Comparing with the Dictionary Way — Why Classes

# Dictionary way: it only has data
pc_old = {"name": "Desktop", "ip": "192.168.0.10", "on": False}
pc_old["on"] = True        # outside code does the powering-on for it, one by one

# Class way: data and behavior live together
pc_new = Host("Desktop", "192.168.0.10")
pc_new.boot()              # you order the thing itself

How to read it: a dictionary is just a bundle of data, so outside code does all the acting on its behalf. A class lets the thing act on its own. The more targets you have, the easier the ordering-the-thing side is to manage. Typos are also caught better on the class side — calling a nonexistent method gets you an immediate AttributeError, while a dictionary key typo often slips by quietly.

3-5. Practicing Methods Changing Data — A Port Scan Record

Extend the Host class (host2.py):

class Host:
    def __init__(self, name, ip):
        self.name = name
        self.ip = ip
        self.on = False
        self.open_ports = []

    def boot(self):
        self.on = True
        print(self.name, "powered on.")

    def add_port(self, port):
        if port not in self.open_ports:
            self.open_ports.append(port)
        print(self.name, "open ports:", self.open_ports)

    def info(self):
        state = "on" if self.on else "off"
        print(self.name, "|", self.ip, "|", state, "| open ports:", self.open_ports)

Run it:

gw = Host("Router", "192.168.0.1")
gw.boot()
gw.add_port(80)
gw.add_port(443)
gw.add_port(80)
gw.info()
Router powered on.
Router open ports: [80]
Router open ports: [80, 443]
Router open ports: [80, 443]
Router | 192.168.0.1 | on | open ports: [80, 443]

(Measured 2026-09-09.)

How to read the output: __init__ prepares an empty list called self.open_ports, and add_port fills it while checking for duplicates. Look how 80, added twice, went in only once. A method managing its own data — the prototype of a scanner.

3-6. Methods Calling Methods — self’s Other Job

self points not only to attributes but also to methods. Add one more method inside the Host class (indented to the same depth as the other methods):

    def check(self):
        if not self.on:
            self.boot()
        self.info()

Run it:

pc = Host("TestBox", "192.168.0.99")
pc.check()
pc.check()
TestBox powered on.
TestBox | 192.168.0.99 | on | open ports: []
TestBox | 192.168.0.99 | on | open ports: []

(Measured 2026-09-09.)

How to read the output: check is the action "if it’s off, power it on (boot), then print the info (info)." self.boot() means "call my boot." On the second pc.check(), it’s already on, so "powered on." does not appear again — because the conditional is guarding the pass.

Why: when methods call methods, you can build a big action as an assembly of small actions. It’s the class version of functions calling functions (Step 44).


4. Missions & Exercises

Mission — My Lab Asset Manager

  1. Based on the Host class above, create three or more instances: router, my PC, virtual machine
  2. Set their power states and open ports differently from each other
  3. Put all the assets in a list and print an "asset status board" that calls info() in a for loop
  4. Add a feature that picks only the powered-on assets and prints their port lists (Step 43 review)
  5. Challenge: make it save the status board to asset_status.txt (Step 45 review)

Finally, write in your notes, in one line, why this way is more convenient than listing dictionaries. That one line is evidence that you understood object orientation.

Exercises

Q1. Explain the difference between a class and an instance with the blueprint/thing analogy, and say what "the thing each instance holds differently" is.

Q2. If you define a method without self, like def info():, and then call pc.info(), what happens? Why, in terms of how Python passes self?

Q3. What happens if you mistype __init__ as _init_, and what error appears afterward?

Q4. In what situations is it better NOT to use a class? State two criteria for "when to pull out a class."


Answers & completion criteria · expand/collapse

5. Model Answers & Completion Criteria

Mission Model Answer

class Host:
    def __init__(self, name, ip):
        self.name = name
        self.ip = ip
        self.on = False
        self.open_ports = []

    def boot(self):
        self.on = True

    def add_port(self, port):
        if port not in self.open_ports:
            self.open_ports.append(port)

    def info(self):
        state = "on" if self.on else "off"
        return f"{self.name} | {self.ip} | {state} | open ports: {self.open_ports}"


assets = [
    Host("Router", "192.168.0.1"),
    Host("MyPC", "192.168.0.10"),
    Host("VM", "192.168.0.50"),
]
assets[0].boot()
assets[0].add_port(80)
assets[0].add_port(443)
assets[1].boot()

lines = ["=== Lab Asset Status ==="]
for h in assets:
    lines.append(h.info())
lines.append("--- Ports on powered-on assets ---")
for h in assets:
    if h.on:
        lines.append(f"{h.name}: {h.open_ports}")

report = "n".join(lines)
print(report)
with open("asset_status.txt", "w", encoding="utf-8") as f:
    f.write(report)

How to verify: ① Does the status board print the three assets, each in a different state? ② Is the powered-off VM missing from the "powered-on assets" list? ③ Was asset_status.txt created? ④ Note that info() was changed to return a string instead of printing — that’s what lets you reuse the same result for screen output and file saving. The instinct of separating "functions that print" from "functions that build" is one you’ll keep using.

Exercise Solutions

Q1 solution. A class is the blueprint (the mold), of which there’s only one; instances are the actual things stamped from that mold, and you can make many. What each instance holds differently is its attribute values (name, IP, power state, etc.). The method definitions are shared, but the state accessed through self is each one’s own.

Q2 solution. An error occurs. When Python calls pc.info(), it secretly inserts the instance pc as the first argument — but if the definition has no self, that argument loses its place to stand. Error measured 2026-09-09: TypeError: Host.info() takes 0 positional arguments but 1 was given. For methods inside a class, the first argument is always self.

Q3 solution. Python doesn’t recognize _init_ as a special method, so it doesn’t run it automatically. It becomes just an ordinary (never-called) method. So you end up in a state without the attributes __init__ was supposed to prepare, and the moment you access one, an error like AttributeError: 'Host' object has no attribute 'on' appears (measured 2026-09-09). Write exactly two underscores on each side.

Q4 solution. A blueprint is overkill for a ten-line one-off script. The criteria for pulling out a class are ① when data and behavior travel together, and ② when you need several of the same kind of thing. For now the goal is getting comfortable with the syntax; the sense of when to use it grows as you write more code.

Completion Criteria Checklist

  • [ ] I can explain the difference between a class (blueprint) and an instance (thing)
  • [ ] I can create attributes and methods with a clear distinction
  • [ ] I can explain when and why __init__ runs automatically
  • [ ] I can explain that self is "the instance that made the call"
  • [ ] I can distinguish and fix a forgotten self (TypeError), a missing self (NameError), and an __init__ typo (AttributeError)
  • [ ] A method can call another method of the same class via self
  • [ ] Mission: I completed the lab asset manager and saved asset_status.txt

6. Common Pitfalls & Fixes

Wall 1. TypeError: takes 0 positional arguments but 1 was given

Symptom (measured 2026-09-09):

TypeError: Host.info() takes 0 positional arguments but 1 was given

Cause: you forgot self in the method definition. If you write def info():, the self Python secretly inserts (that "1") loses its place to stand.
Fix: for methods inside a class, the first argument is always self — def info(self):. This error is a rite of passage every beginner goes through once.

Wall 2. NameError: name ‘name’ is not defined

Symptom (measured 2026-09-09):

NameError: name 'name' is not defined. Did you mean: 'self.name'?

Cause: inside a method, you wrote self.name as just name. Your own attributes must always be pointed to through self.
Fix: memorize the spell: "if it’s mine, self." These days Python even shows the answer as a hint, like in the measured message — the habit of reading the last line of an error message is the answer itself.

Wall 3. Using the class without parentheses

Symptom: you write only pc = Host, and calling pc.boot() raises a strange error.
Cause: Host is the blueprint itself. You must attach parentheses Host(...) for an instance to be stamped out.
Fix: you can’t give orders to a blueprint. The parentheses are the command "stamp one out!" — pc = Host("name", "IP").

Wall 4. Mistyping the init name

Symptom: you made an instance, but you get an error saying there’s no attribute (measured 2026-09-09):

AttributeError: 'Host' object has no attribute 'on'

Cause: if you get the underscores wrong, like _init_ or init, Python sees it as an ordinary method and doesn’t run it automatically.
Fix: two underscores on each side, __init__. Write the special method’s marker exactly.

Wall 5. Indentation tangles when adding to a class

Symptom: you added the check from 3-6 and got an IndentationError or a "no such method" error.
Cause: methods belong inside the class, and a method’s body inside that — indentation depth decides membership.
Fix: check that check’s def line is at the same depth (4 spaces) as the other methods’ def lines. Written outside the class, it becomes just a function.


7. Summary

Today’s Concepts

Concept One-line description
Class The blueprint (mold) — only one
Instance A thing stamped from the mold — each state is its own
Attribute The data a thing remembers
Method An action a thing can perform
self The pronoun pointing to "myself" inside a method
__init__ Initial setup that runs automatically when an instance is born

Today’s Syntax

Syntax What it does
class Host: Start of the blueprint
def __init__(self, name, ip): Runs automatically at creation, plants first values
self.name = name Store a value in my attribute
pc = Host("name", "IP") Stamp out an instance
pc.boot() Order the thing to act
self.boot() Call my other method from inside a method

The Instinct That Matters More Than Commands

When making a class, don’t type code first. Two lines in your notes first: (a) what this thing will remember — name, IP, power, port list. (b) what this thing will do — power on, record ports, print info. (a) is the candidate list for attributes and (b) for methods. These two lines are the skeleton of the class.

Let’s organize today’s harvest in two. First, the error-message reading chart: TypeError means argument count, NameError means a name, AttributeError means an attribute/method spelling. You met all three in today’s measurements, and the faster you read them, the more you become the owner of errors. Second, open the code of famous security tools and it’s all what you learned today — a Scanner class, a Target class, a Report class. Once you can read classes, other people’s tool code changes from "a manual you use" to "a blueprint you modify." The difference between people who make tools and people who use tools splits right here.


Once every box is checked, Step 52 is complete.

ONE STEP FURTHER

Finished this lesson?

Check the completion criteria, then mark your progress.

Something wrong with this page or a link? Let us know.
Next