Skip to main content

Static Methods and Class Methods

In Lesson 1, James replaced word_count with a @property. Now he looks at a function sitting outside the Note class:

def note_from_dict(data: dict[str, str]) -> Note:
return Note(
title=data["title"],
body=data["body"],
author=data.get("author", "Anonymous"),
)

He uses it to load notes from JSON files. It works. But it feels disconnected.

"That belongs on the Note class," Emma says. "When you load a note from a dictionary, you are creating a Note. The creation logic should live where the class lives."


If you're new to programming

A factory method is a method that creates and returns a new object. Instead of calling Note(title, body) directly, you call Note.from_dict(data) and the method handles the construction details. Factory methods give you multiple ways to create the same type of object.

If you've coded before

Python's @classmethod receives cls (the class itself) as its first argument, enabling alternative constructors that work correctly with inheritance. @staticmethod receives neither self nor cls and is essentially a function namespaced to the class. Both are descriptors under the hood.


@classmethod: Alternative Constructors

A @classmethod receives the class itself as its first argument, conventionally named cls. This lets it create new instances:

class Note:
def __init__(self, title: str, body: str, author: str = "Anonymous") -> None:
self.title = title
self.body = body
self.author = author
self.is_draft = True
self.tags: list[str] = []

@classmethod
def from_dict(cls, data: dict[str, str]) -> "Note":
return cls(
title=data["title"],
body=data["body"],
author=data.get("author", "Anonymous"),
)
data = {"title": "Inventory Check", "body": "Count shelf B4", "author": "James"}
note = Note.from_dict(data)
print(note.title)
print(note.author)

Output:

Inventory Check
James

Why cls, Not Note?

The method uses cls(...) instead of Note(...). This matters for inheritance. If TaskNote inherits from Note, calling TaskNote.from_dict(data) passes TaskNote as cls, so you get a TaskNote instance back, not a plain Note. Using cls makes the factory work for the whole hierarchy.

Common Factory Methods

Factory MethodCreates FromExample
from_dict(data)DictionaryLoading from JSON/API response
from_string(text)Formatted stringParsing `"Title
from_file(path)File pathReading a saved note

The pattern is always the same: take some input format, extract the pieces, call cls(...) to create the instance.


@staticmethod: Utility Functions

A @staticmethod does not receive self or cls. It is a function that logically belongs in the class namespace but does not need access to any instance or class state:

class Note:
# ... __init__, from_dict, etc.

@staticmethod
def is_valid_title(title: str) -> bool:
return bool(title.strip()) and len(title) <= 200
print(Note.is_valid_title("Warehouse Tips"))
print(Note.is_valid_title(""))
print(Note.is_valid_title("x" * 201))

Output:

True
False
False

When to Use @staticmethod

QuestionIf YesIf No
Does the function need self (instance data)?Regular methodKeep reading
Does the function need cls (to create instances)?@classmethodKeep reading
Does the function logically belong to this class?@staticmethodStandalone function

James's analogy: a @staticmethod is like a tool that sits in the warehouse but does not need to know which warehouse it is in. A wrench is a wrench regardless of the building. But it belongs in the tool cabinet (the class), not scattered on the floor (a standalone function).


The Decision Matrix

ScenarioMethod TypeFirst ParameterExample
Reads or modifies instance stateRegular methodselfnote.add_tag("python")
Creates a new instance from alternative input@classmethodclsNote.from_dict(data)
Utility function related to the class@staticmethod(none)Note.is_valid_title("Hi")
Unrelated to the classStandalone function(none)format_date(timestamp)

The key question: does this function need access to instance state (self), class state (cls), or neither?


PRIMM-AI+ Practice: Method Types

Predict [AI-FREE]

Press Shift+Tab to enter Plan Mode before predicting.

For each function, predict which method type it should be. Write your prediction and a confidence score from 1 to 5.

  1. word_count(self) -> int -- returns len(self.body.split())
  2. from_json(cls, json_string: str) -> "Note" -- parses JSON and creates a Note
  3. is_valid_tag(tag: str) -> bool -- checks if a tag is lowercase and under 20 characters
  4. summarize(self) -> str -- returns a formatted string from self.title and self.tags
  5. generate_id() -> str -- returns a UUID string, no instance or class needed
Check your predictions
  1. Regular method (or @property). Needs self to access self.body.
  2. @classmethod. Needs cls to create a new instance: cls(...).
  3. @staticmethod. No self or cls needed, but logically belongs to Note.
  4. Regular method. Needs self to access self.title and self.tags.
  5. @staticmethod or standalone function. No self or cls. Could go either way depending on whether it logically belongs to Note.

Run

Press Shift+Tab to exit Plan Mode.

Create a file called methods_practice.py. Write a Note class with all five functions above, using the correct method type. Run uv run pyright methods_practice.py to verify the type signatures are correct.

Investigate

In Claude Code, type:

/investigate @methods_practice.py

Ask: "If I change from_json from @classmethod to @staticmethod, what breaks? What error would I see when calling Note.from_json(json_string)?"


Try With AI

Prompt 1: Refactor Standalone Functions

I have these standalone functions that create Note objects:

def note_from_dict(data: dict) -> Note:
return Note(title=data["title"], body=data["body"])

def note_from_csv_row(row: list[str]) -> Note:
return Note(title=row[0], body=row[1], author=row[2])

def note_from_markdown(md: str) -> Note:
lines = md.strip().split("\n")
title = lines[0].lstrip("# ")
body = "\n".join(lines[1:])
return Note(title=title, body=body)

Convert each to the appropriate method type on the
Note class. Explain why each should be @classmethod
and not @staticmethod.

The AI should explain that all three need cls(...) to create instances correctly with inheritance.

What you're learning: You are practicing the decision rule (needs cls to create instance = classmethod) across multiple factory scenarios, reinforcing the pattern.

Prompt 2: When @staticmethod Goes Wrong

Show me a case where someone uses @staticmethod when they
should use @classmethod. What bug does this create when
the class is subclassed? Show the bug in action with a
concrete example.

What you're learning: The AI demonstrates the inheritance failure that happens when a factory uses Note(...) instead of cls(...). This makes the cls parameter feel necessary, not arbitrary.

Prompt 3: Organize Methods for Your Domain

I work in [describe your professional domain]. I have a
class for [describe a key entity in your domain]. It has
these standalone functions that should probably be methods:

[list 2-3 functions]

For each, recommend: regular method, @classmethod,
@staticmethod, or keep as standalone. Explain your
reasoning using the decision matrix.

What you're learning: You are transferring the method-type decision framework to your own professional context. The AI provides recommendations, but you evaluate whether its reasoning matches the decision matrix.


Emma looks at the refactored Note class. Note.from_dict(data) sits cleanly as a @classmethod. Note.is_valid_title(title) is a @staticmethod.

"I was uncertain about is_valid_title," she says. "It could be a standalone function. I would not argue with someone who kept it outside the class. But it validates Note titles specifically, so I prefer it inside the Note namespace. It is a judgment call, not a rule."

James thinks about his warehouse software. "We had a function called validate_sku_format() that sat in a utility file. Nobody could find it. When we moved it onto the Product class as a static method, three teams started using it the same week. Proximity matters."

"That is the right way to think about it. If the function serves the class, it lives on the class. If it serves the module, it lives in the module."

"Properties compute. Static and class methods organize. What about logic that applies to every method? I want to log every method call, but I do not want to add a print statement to each one."

"That is a decorator. Next lesson."