Chapter 9: Classes

Classes model real-world things. They combine data (attributes) and behavior (methods).

Creating a Class

class Server:
    """Represents a server in the infrastructure."""

    def __init__(self, hostname, ip):  (1)
        """Initialize server attributes."""
        self.hostname = hostname       (2)
        self.ip = ip
        self.status = 'stopped'        (3)

    def start(self):                   (4)
        """Start the server."""
        self.status = 'running'
        print(f"{self.hostname} started")

    def stop(self):
        """Stop the server."""
        self.status = 'stopped'
        print(f"{self.hostname} stopped")
1 init runs automatically when creating instance
2 self.attribute stores data on the instance
3 Default value - no argument needed
4 Methods are functions that belong to the class

Creating Instances

web = Server('web-01', '10.0.1.10')  (1)
db = Server('db-01', '10.0.2.10')

print(web.hostname)  # 'web-01'
print(web.ip)        # '10.0.1.10'
print(web.status)    # 'stopped'
1 Python calls init with 'web-01' and '10.0.1.10'

Calling Methods

web.start()          # web-01 started
print(web.status)    # 'running'
web.stop()           # web-01 stopped

Multiple Instances

Each instance is independent:

web = Server('web-01', '10.0.1.10')
db = Server('db-01', '10.0.2.10')

web.start()
print(web.status)    # 'running'
print(db.status)     # 'stopped' - unaffected

Modifying Attributes

Direct Access

web.status = 'maintenance'

Through Methods

Better control - can add validation:

class Server:
    # ...

    def set_status(self, status):
        """Set status with validation."""
        valid = ['running', 'stopped', 'maintenance']
        if status in valid:
            self.status = status
        else:
            print(f"Invalid status: {status}")

Incrementing

class Server:
    def __init__(self, hostname, ip):
        self.hostname = hostname
        self.ip = ip
        self.request_count = 0

    def log_request(self):
        """Increment request counter."""
        self.request_count += 1

Inheritance

Create specialized versions of existing classes.

class WebServer(Server):  (1)
    """A web server with HTTP-specific features."""

    def __init__(self, hostname, ip, port=80):
        super().__init__(hostname, ip)  (2)
        self.port = port                (3)
        self.ssl = port == 443

    def serve(self):
        """Start serving HTTP requests."""
        protocol = 'https' if self.ssl else 'http'
        print(f"Serving at {protocol}://{self.ip}:{self.port}")
1 WebServer inherits from Server
2 super().init() calls parent’s init
3 Add child-specific attributes
nginx = WebServer('web-01', '10.0.1.10', 443)
nginx.start()       # Inherited from Server
nginx.serve()       # WebServer-specific
print(nginx.ssl)    # True

Overriding Methods

Child can replace parent methods:

class WebServer(Server):
    # ...

    def start(self):
        """Start with HTTP-specific message."""
        self.status = 'running'
        print(f"{self.hostname} listening on port {self.port}")

Now WebServer.start() replaces Server.start().

Composition

Use instances as attributes - "has a" relationship.

class Database:
    """Database connection settings."""

    def __init__(self, host, port=5432):
        self.host = host
        self.port = port
        self.connected = False

    def connect(self):
        self.connected = True
        print(f"Connected to {self.host}:{self.port}")


class Application:
    """Application with database."""

    def __init__(self, name, db_host):
        self.name = name
        self.database = Database(db_host)  (1)

    def start(self):
        self.database.connect()
        print(f"{self.name} running")
1 Application has a Database instance
app = Application('myapp', '10.0.2.10')
app.start()
# Connected to 10.0.2.10:5432
# myapp running

app.database.connected  # True

Importing Classes

Single Class

# server.py
class Server:
    # ...
# main.py
from server import Server

web = Server('web-01', '10.0.1.10')

Multiple Classes

# Put related classes in one module
from infrastructure import Server, WebServer, Database

Or import the module:

import infrastructure as infra

web = infra.WebServer('web-01', '10.0.1.10')
db = infra.Database('db-01')

Standard Library Classes

Python includes useful classes. Example with random:

from random import randint, choice

# Random integer between 1 and 100
port = randint(1024, 65535)

# Random choice from list
server = choice(['web-01', 'web-02', 'web-03'])

Class Patterns

Service with Multiple States

class Service:
    STATES = ['stopped', 'starting', 'running', 'stopping']

    def __init__(self, name):
        self.name = name
        self.state = 'stopped'

    def transition(self, new_state):
        if new_state in self.STATES:
            print(f"{self.name}: {self.state} -> {new_state}")
            self.state = new_state

Configuration Object

class Config:
    def __init__(self, **settings):
        for key, value in settings.items():
            setattr(self, key, value)  (1)

    def __repr__(self):  (2)
        items = [f"{k}={v!r}" for k, v in self.__dict__.items()]
        return f"Config({', '.join(items)})"
1 Dynamically set attributes from kwargs
2 Custom string representation
cfg = Config(host='10.0.1.10', port=443, ssl=True)
print(cfg.host)  # 10.0.1.10
print(cfg)       # Config(host='10.0.1.10', port=443, ssl=True)

Styling Classes

class ClassName:  (1)
    """Class docstring."""

    def __init__(self, param):
        """Initialize attributes."""
        self.attribute = param

    def method_name(self):  (2)
        """Method docstring."""
        pass
1 Class names: CamelCase
2 Method names: snake_case

Two blank lines before and after class definitions.

Quick Reference

Concept Code

Define class

class Name:

Constructor

def init(self, …​):

Instance attribute

self.attr = value

Method

def method(self):

Create instance

obj = ClassName(args)

Access attribute

obj.attr

Call method

obj.method()

Inheritance

class Child(Parent):

Call parent init

super().init(…​)

Import class

from module import Class

Exercises

9-1. Server

Create Server class with hostname, ip, status. Add describe() method.

9-2. Database Server

Create DatabaseServer(Server) with additional engine attribute (postgres, mysql).

9-3. Service Counter

Create Service class tracking request count. Methods: handle_request(), get_stats().

9-4. User

Create User class with username, login_attempts. Methods: increment_login_attempts(), reset_login_attempts().

9-5. Composition

Create Cluster class containing a list of Server instances. Method: status() showing all servers.

9-6. Module

Store your classes in infrastructure.py. Import and use them.

Summary

  • Classes combine data (attributes) and behavior (methods)

  • init initializes new instances

  • self refers to the current instance

  • Inheritance: child classes extend parent classes

  • super() calls parent class methods

  • Composition: classes can contain other class instances

  • CamelCase for class names, snake_case for methods

Next: Working with files and handling exceptions.