Learnixo
Back to blog
AI Systemsintermediate

Classes and Objects

Build Python classes for AI engineering: instance attributes, class attributes, methods, properties, encapsulation, and real-world patterns from LangChain and ML codebases.

Asma Hafeez KhanMay 16, 20265 min read
PythonClassesOOPObjectsInstance MethodsProperties
Share:𝕏

Why Classes?

A class groups related data and behavior together. Instead of passing five separate variables to every function, you bundle them into an object:

Python
# Without class: unwieldy
def calculate_dose(drug_name, base_dose, weight, renal_factor, age_factor):
    return base_dose * weight * renal_factor * age_factor

# With class: organized, self-documenting
class DoseCalculator:
    def __init__(self, drug_name: str, base_dose_per_kg: float):
        self.drug_name = drug_name
        self.base_dose_per_kg = base_dose_per_kg

    def calculate(self, weight_kg: float, renal_factor: float = 1.0, age_factor: float = 1.0) -> float:
        return self.base_dose_per_kg * weight_kg * renal_factor * age_factor

Defining a Class

Python
class Drug:
    """Represents a medication with its clinical properties."""

    # Class attribute: shared by all instances
    reference_database = "Lexicomp"

    def __init__(self, name: str, category: str, dose_mg: float):
        # Instance attributes: unique to each object
        self.name = name
        self.category = category
        self.dose_mg = dose_mg
        self._interactions: list[str] = []   # Private by convention (underscore)

    def add_interaction(self, other_drug: str) -> None:
        """Add a known interacting drug."""
        self._interactions.append(other_drug)

    def has_interaction_with(self, other_drug: str) -> bool:
        """Check if this drug interacts with another."""
        return other_drug.lower() in [d.lower() for d in self._interactions]

    def __str__(self) -> str:
        """Human-readable string representation."""
        return f"{self.name} ({self.category}, {self.dose_mg}mg)"

    def __repr__(self) -> str:
        """Developer-facing representation — useful in debuggers."""
        return f"Drug(name={self.name!r}, category={self.category!r}, dose_mg={self.dose_mg})"


# Creating instances
warfarin = Drug(name="warfarin", category="anticoagulant", dose_mg=5.0)
aspirin  = Drug(name="aspirin", category="nsaid", dose_mg=81.0)

# Accessing attributes
print(warfarin.name)          # "warfarin"
print(warfarin.dose_mg)       # 5.0
print(Drug.reference_database) # "Lexicomp"  class attribute via class
print(warfarin.reference_database)  # "Lexicomp"  also accessible via instance

# Calling methods
warfarin.add_interaction("aspirin")
print(warfarin.has_interaction_with("aspirin"))  # True
print(warfarin.has_interaction_with("metformin"))  # False

# __str__ called by print() and str()
print(warfarin)   # "warfarin (anticoagulant, 5.0mg)"

Instance vs Class Attributes

Python
class LLMConfig:
    # Class attribute: shared default
    default_model = "gpt-4o"
    call_count = 0   # Shared counter across all instances  careful!

    def __init__(self, model: str | None = None, temperature: float = 0.0):
        # Instance attribute: per-object
        self.model = model or LLMConfig.default_model
        self.temperature = temperature

    def invoke(self, prompt: str) -> str:
        LLMConfig.call_count += 1   # Mutate class attribute via class name
        return f"Response from {self.model}"


config_a = LLMConfig()
config_b = LLMConfig(model="claude-sonnet-4-6", temperature=0.3)

print(config_a.model)   # "gpt-4o"
print(config_b.model)   # "claude-sonnet-4-6"

config_a.invoke("Hello")
config_b.invoke("Hello")
print(LLMConfig.call_count)   # 2  shared across all instances

Properties: Computed and Validated Attributes

Python
class PatientRecord:
    def __init__(self, patient_id: str, weight_kg: float, height_cm: float):
        self.patient_id = patient_id
        self._weight_kg = weight_kg
        self._height_cm = height_cm

    @property
    def weight_kg(self) -> float:
        """Weight in kilograms."""
        return self._weight_kg

    @weight_kg.setter
    def weight_kg(self, value: float) -> None:
        if value <= 0 or value > 500:
            raise ValueError(f"Invalid weight: {value}kg")
        self._weight_kg = value

    @property
    def bmi(self) -> float:
        """Body mass index — computed from weight and height."""
        height_m = self._height_cm / 100
        return round(self._weight_kg / (height_m ** 2), 1)

    @property
    def bmi_category(self) -> str:
        if self.bmi < 18.5:
            return "underweight"
        elif self.bmi < 25:
            return "normal"
        elif self.bmi < 30:
            return "overweight"
        return "obese"


patient = PatientRecord("P001", weight_kg=80.0, height_cm=175.0)
print(patient.bmi)           # 26.1  computed, no attribute stored
print(patient.bmi_category)  # "overweight"

patient.weight_kg = 75.0   # Calls the setter  validated
patient.weight_kg = -10    # Raises ValueError

patient.bmi = 25.0   # AttributeError: bmi has no setter (read-only)

Class Methods and Static Methods

Python
class Drug:
    _registry: dict[str, "Drug"] = {}   # Class-level drug registry

    def __init__(self, name: str, category: str):
        self.name = name
        self.category = category
        Drug._registry[name.lower()] = self

    @classmethod
    def from_dict(cls, data: dict) -> "Drug":
        """Alternative constructor — create Drug from a dict."""
        return cls(name=data["name"], category=data["category"])

    @classmethod
    def lookup(cls, name: str) -> "Drug | None":
        """Look up a drug by name."""
        return cls._registry.get(name.lower())

    @staticmethod
    def normalize_name(name: str) -> str:
        """Normalize a drug name — no access to class or instance."""
        return name.lower().strip()


# Class method as alternative constructor
warfarin = Drug.from_dict({"name": "warfarin", "category": "anticoagulant"})
aspirin  = Drug.from_dict({"name": "aspirin", "category": "nsaid"})

# Class method as factory
found = Drug.lookup("warfarin")
print(found.category)   # "anticoagulant"

# Static method  utility function
print(Drug.normalize_name("  WARFARIN  "))   # "warfarin"

Real Pattern: RAG Session Manager

Python
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_core.messages import HumanMessage, AIMessage

class ClinicalRAGSession:
    """A per-session RAG chatbot with isolated history."""

    _model = ChatOpenAI(model="gpt-4o", temperature=0)   # Shared across sessions
    _embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

    def __init__(self, session_id: str, user_id: str, max_history: int = 10):
        self.session_id = session_id
        self.user_id = user_id
        self._max_history = max_history
        self._history: list = []
        self._vectorstore = Chroma(
            collection_name="clinical_knowledge",
            embedding_function=self._embeddings,
            persist_directory="./chroma_db",
        )

    @property
    def history_length(self) -> int:
        return len(self._history)

    def chat(self, question: str) -> str:
        """Process a question and return an answer."""
        # Retrieve
        docs = self._vectorstore.similarity_search(question, k=3)
        context = "\n\n".join(d.page_content for d in docs)

        # Build messages
        messages = [
            {"role": "system", "content": f"Answer using this context:\n{context}"},
            *self._history[-self._max_history:],
            {"role": "user", "content": question},
        ]

        # Generate
        response = self._model.invoke(messages)
        answer = response.content

        # Update history
        self._history.extend([
            {"role": "user", "content": question},
            {"role": "assistant", "content": answer},
        ])

        return answer

    def reset(self) -> None:
        self._history.clear()

    def __repr__(self) -> str:
        return f"ClinicalRAGSession(session_id={self.session_id!r}, turns={self.history_length})"


session = ClinicalRAGSession(session_id="sess_001", user_id="pharmacist_1")
answer1 = session.chat("What is warfarin?")
answer2 = session.chat("What are its interactions?")   # History-aware
print(repr(session))   # ClinicalRAGSession(session_id='sess_001', turns=2)

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.