After seven parts of scripting and automation, it is time to improve your code structure so your projects stay reusable and maintainable. We'll bundle state and behavior with classes and apply the dataclasses module to build data-focused objects quickly.
Key terms
- Instance: A concrete object created from a class;
selfpoints to this instance. - dataclass: A standard-library decorator that auto-generates constructors and comparison methods for data containers.
- slots: A Python feature that locks down the allowed attribute names to save memory and catch typos.
- Protocol: A typing tool that declares required methods so different implementations can share the same interface.
Core ideas
Study memo
- Time: 60 minutes
- Prereqs: Comfortable splitting code into functions/modules plus experience with requests and file IO
- Goal: Define classes that tie state+behavior together and use
dataclassmodels with conversion helpers
Classes combine state and behavior. Dataclasses reduce boilerplate for data containers so you can focus on transformations.
Code examples
A minimal class
class MealFetcher:
def __init__(self, school_code: str):
self.school_code = school_code
def build_url(self, date: str) -> str:
return f"https://school.example/api/meals/{self.school_code}?date={date}"
def parse(self, raw: dict) -> list[str]:
return raw["menu"]
This ties the shared state (self.school_code) and reusable methods so your CLI or batch job calls the same logic everywhere.
Instantiate and call methods
fetcher = MealFetcher("demo-high")
today_menu = fetcher.parse({"menu": ["Pork cutlet", "Salad"]})
self always references the instance, so you never pass it manually. Pairing classes with typing keeps IDE suggestions and type checking happy.
Class methods and factory helpers
from datetime import date
class MealFetcher:
@classmethod
def for_today(cls, school_code: str) -> "MealFetcher":
instance = cls(school_code)
instance.query_date = date.today().isoformat()
return instance
Use @classmethod for alternative constructors, like injecting API tokens or shared config.
Define data models with dataclasses
from dataclasses import dataclass
@dataclass(slots=True)
class Meal:
menu: list[str]
calories: int
origin: list[str]
@dataclass auto-generates __init__, __repr__, and comparisons. slots=True shrinks memory usage and prevents accidental attributes.
🧩 dataclass in one line A decorator that builds constructor + representation methods for data-centric classes.
Defaults and conversion helpers
from dataclasses import dataclass, field
@dataclass
class Meal:
menu: list[str]
calories: int = 0
origin: list[str] = field(default_factory=list)
@classmethod
def from_json(cls, payload: str) -> "Meal":
data = json.loads(payload)
return cls(menu=data["menu"], calories=data.get("calories", 0))
def to_markdown(self) -> str:
return "\n".join(f"- {item}" for item in self.menu)
field(default_factory=list) is mandatory for mutable defaults. Conversion helpers like from_json and to_markdown become bridges between API responses and presentations.
Inheritance and protocols
from typing import Protocol
class Notifier(Protocol):
def notify(self, message: str) -> None: ...
class SlackNotifier:
def __init__(self, webhook: str):
self.webhook = webhook
def notify(self, message: str) -> None:
print("send to slack", message)
Protocols capture the "what" (method signature) without dictating the "how". They're perfect when you want duck typing plus static guarantees, such as swapping notification channels or injecting test doubles.
📌 What is a Protocol? An interface declaration that lists required attributes/methods. Implementations stay flexible while guaranteeing behavior.
Diagramming responsibilities shows who creates data, who formats it, and where protocols abstract the interaction.
Why it matters
Separating responsibilities with classes lets you evolve API calls, parsing, and notifications independently. Dataclass conversion helpers bridge JSON and Markdown so your automation stays understandable to humans.
Practice
- Follow along: Implement
MealFetcherandMeal, then confirmto_markdown()prints the expected list. - Extend: Create
ConsoleNotifierandFileNotifierclasses that fulfill theNotifierprotocol, then inject them intoMealFetcher. - Debug: Intentionally set a list literal as the default in a dataclass, observe the mutable-default warning, and fix it with
field(default_factory=list). - Done when: At least two classes collaborate while a dataclass conversion method round-trips JSON strings and objects.
Wrap up
Once you structure data with classes and dataclasses, API calls, caching, and notifications fall into clear modules. Next we'll reinforce that structure with robust error handling and logging strategies.
💬 댓글
이 글에 대한 의견을 남겨주세요