Choose classmethod or staticmethod with a Parser
Use a classmethod for an alternative constructor and a staticmethod for class-related validation without instance state.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Choose classmethod or staticmethod with a Parser
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Python offers instance methods, class methods, and static methods, and the names can sound interchangeable until inheritance exposes a real difference. Imagine a Reading class with an alternative constructor that accepts a line of text. It splits Ada colon seven, stores a name and count, and appears to work. Later, a SpecialReading subclass inherits that constructor. Calling it on the subclass produces the right field values but unexpectedly returns a plain Reading. A test that checks only the fields may stay green while code that depends on the subclass fails elsewhere. In this lesson we will run that broken constructor, inspect why a staticmethod
- 00:42
cannot know which class called it, then repair it with a classmethod that receives cls. We will also give staticmethod a job it does well: validating a count without touching instance or class state. Finally we will test malformed text, a negative value, a boolean passed to the helper, and the inherited constructor. The matching PythonLessonLab exercise lets you complete these two method bodies yourself. Watch the printed type in the first demonstration, because that is the detail a superficial happy-path check would miss. The values Ada and seven are correct, which is why this mistake can survive a casual test.
- 01:24
The type is not SpecialReading. The staticmethod receives only the text we passed; Python does not give it an instance or a class automatically. The code therefore names Reading explicitly in the return statement, and it always constructs Reading. Calling through a subclass does not rewrite that hard-coded name. We could override the method in every subclass, but that repeats parsing logic and gives future subclasses another place to drift. We should first ask what this operation means: from_text is a way to construct an instance of whichever class it is called on. That is a classmethod's purpose. An instance method would require an existing object before we could construct
- 02:07
one, which defeats the alternative constructor. A staticmethod is not bad in general; it simply lacks the cls parameter needed here. Keep this output in mind as we change one decorator and one return expression in the repair. The static helper asks one question about the value it receives. It does not need an existing Reading or the class on which it was called. That makes it a reasonable staticmethod related to this domain. Notice the exact-type check: in Python, bool is a subclass of int, so `isinstance(True, int)` is true. For a count where True makes no sense as one, we explicitly require the exact int type.
- 02:49
The classmethod is different. Python supplies cls as the class used to call it. When we call caller_name on SpecialReading, it returns SpecialReading. This is the information an inherited alternative constructor needs. Instance methods, by comparison, receive self and are used when operating on an existing object. The choice is about what data a method needs, not which decorator looks more concise. We can now write from_text as a classmethod, use cls to construct the result, and call a separate validation helper to check the parsed value before any object exists. The parser now receives cls, splits at the first colon, removes surrounding spaces,
- 03:32
and checks for a name. Converting the numeric part with int rejects malformed number text. The static helper rejects a negative count, and the classmethod finally calls cls with the validated values. That final line is the important inheritance repair: when called on SpecialReading, cls means SpecialReading. The printed type confirms it. We are also careful about when validation happens. We do not construct a partial object and then discover that its name is empty or its count invalid. We reject bad external text before calling the constructor.
- 04:12
The sample keeps the contract intentionally narrow: name colon integer, with a nonnegative count. If a real file format allowed colons inside names or decimal counts, we would need a different parser and tests for those decisions. For this lesson, one clear format is enough to show the distinct purposes of a classmethod constructor and a staticmethod helper. The first two assertions verify more than a successful parse: they confirm the exact subclass and its stored fields. Next, the static helper rejects True instead of treating it as a count of one. The loop then checks three different external-input failures: a blank name, a
- 04:52
word where an integer is expected, and a negative integer. Each must raise ValueError. If any slips through, the final success line is not printed. These checks are deliberately separate because parsing, domain validation, and subclass construction can fail independently. In the companion task, complete valid_value and from_text without copying the whole solution. The hidden checks call the method on a subclass, so hard-coding Reading will fail even if the prepared print looks correct. If you are unsure which decorator to use in another class, ask whether the method needs one instance, the calling class, or neither.
- 05:33
Use an instance method for the first, a classmethod for the second, and a staticmethod only when the function belongs with the class but needs no automatic self or cls. That decision is easier to defend than memorizing decorator names in isolation.
Understand the concept
An instance method receives self and works with one object. A classmethod receives cls and can create an instance of the class on which it was called. That makes it a good fit for an alternative constructor such as `Reading.from_text('Ada:7')`. If a subclass inherits the method, cls is the subclass, so the result can remain that subclass rather than silently becoming a base Reading.
A staticmethod receives neither self nor cls automatically. It is a regular function kept on the class because the operation belongs to the class's concept. Our `valid_value` helper checks that a parsed count is an ordinary nonnegative integer, rejecting bool even though bool is a subclass of int. Calling it as `Reading.valid_value(value)` makes its role discoverable, but it cannot construct the correct subclass by itself because it has no cls argument.
The parser splits the text at the first colon, strips the name and number, and rejects missing pieces. `int` converts the numeric part; malformed text raises ValueError. Then the helper checks the domain rule and the classmethod returns `cls(name, value)`. Keep parsing and validation near the boundary. Do not bury a broad `except Exception` inside the constructor, because that would conceal unrelated bugs and make error messages less useful.
The exercise leaves two method bodies to complete. The prepared input prints a Reading object label and a valid boolean. Hidden checks cover a subclass calling from_text, an empty name, a negative count, a boolean passed to the static helper, and malformed text. Run the deliberate staticmethod mistake in the guide first; it constructs Reading even when invoked from a subclass. Then change the constructor to classmethod and verify the returned type.
- Instance methods receive self
- Class methods receive cls and preserve subclass construction
- Static methods receive no implicit first argument
- Parse external text and validate domain rules before constructing
See it step by step
Read the code, predict the output, then compare it with the result.
01. A static helper has no instance
class Ticket:
@staticmethod
def valid_count(value):
return type(value) is int and value >= 0
print(Ticket.valid_count(0))
print(Ticket.valid_count(True))True
FalseThe helper expresses a domain rule without reading or changing object state.
02. A classmethod builds the requested class
class Label:
def __init__(self, text):
self.text = text
@classmethod
def from_upper(cls, text):
return cls(text.lower())
class SpecialLabel(Label):
pass
item = SpecialLabel.from_upper("HELLO")
print(type(item).__name__, item.text)SpecialLabel hellocls refers to SpecialLabel when the inherited constructor is called on that subclass.
03. Reject bad external text explicitly
class Record:
def __init__(self, name, amount):
self.name, self.amount = name, amount
@classmethod
def from_text(cls, text):
name, separator, amount = text.partition(":")
if not separator or not name.strip():
raise ValueError("expected name:amount")
return cls(name.strip(), int(amount))
try:
Record.from_text(":3")
except ValueError as error:
print(error)expected name:amountA constructor boundary can reject malformed input before creating a partial object.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Expose a constructor that loses the subclass
A staticmethod can parse text, but it receives neither the instance nor the class used to call it. This broken alternative constructor hard-codes Reading in its return expression. When SpecialReading inherits from Reading and calls from_text, the method still returns a Reading. The values look right, so a caller that checks only fields might miss the type error.
Print the returned type to make the failure visible. A staticmethod is still useful for a pure class-related helper, such as validating a nonnegative count. It is the wrong default for this constructor because construction should follow the caller's class. The repair will use classmethod and its cls argument, preserving inherited behavior.
class Reading:
def __init__(self, name, value):
self.name, self.value = name, value
@staticmethod
def from_text(text):
name, value = text.split(":", 1)
return Reading(name, int(value))
class SpecialReading(Reading):
pass
item = SpecialReading.from_text("Ada:7")
print(type(item).__name__, item.name, item.value)Reading Ada 7- Call the inherited constructor on SpecialReading.
- Compare the printed type with the class used for the call.
Use cls for construction and a static helper for a rule
The classmethod receives cls automatically. The parser can split the text at the first colon, strip both pieces, convert the count, and return cls(name, value). When inherited by SpecialReading, cls is SpecialReading. The class name is no longer hard-coded into the constructor body, so subclass construction works without duplicating the parser.
The staticmethod valid_value is a separate operation: it checks the domain rule without reading self or cls. We reject bool because `isinstance(True, int)` would otherwise accept it as the integer one. A stateless rule is easy to test directly. Keep parsing errors explicit and reject an empty name before constructing an object.
class Reading:
def __init__(self, name, value):
self.name, self.value = name, value
@staticmethod
def valid_value(value):
return type(value) is int and value >= 0
@classmethod
def from_text(cls, text):
name, separator, raw = text.partition(":")
if not separator or not name.strip():
raise ValueError("expected name:value")
value = int(raw.strip())
if not cls.valid_value(value):
raise ValueError("value must be nonnegative")
return cls(name.strip(), value)
class SpecialReading(Reading):
pass
item = SpecialReading.from_text(" Ada : 7 ")
print(type(item).__name__, item.name, item.value)SpecialReading Ada 7- Trace cls from the subclass call into the return expression.
- Check that the static helper rejects invalid values before an instance is made.
Test malformed text and exact integer rules
A constructor has to decide what it accepts. The form name:value needs both a nonempty name and an integer value at least zero. Unknown text must not be coerced into a partial Reading. This code uses a compact version of the class to show the boundary cases, including an empty name and a negative number. The actual exercise asks you to implement the same contract yourself.
Notice that text parsing with int produces ordinary ints, but the static helper may also be called directly with True or a float. It should return False for both. A subclass inherits the helper and constructor, yet `cls(...)` still creates the subclass. These tests cover different concerns: parsing, validation, and inheritance. Passing one does not prove the others.
class Reading:
def __init__(self, name, value):
self.name, self.value = name, value
@staticmethod
def valid_value(value):
return type(value) is int and value >= 0
@classmethod
def from_text(cls, text):
name, sep, raw = text.partition(":")
if not sep or not name.strip():
raise ValueError("expected name:value")
value = int(raw.strip())
if not cls.valid_value(value):
raise ValueError("value must be nonnegative")
return cls(name.strip(), value)
print(Reading.from_text("Bo:0").name)
print(Reading.valid_value(True))
for text in (":7", "Ada:-1"):
try:
Reading.from_text(text)
except ValueError as error:
print(error)Bo
False
expected name:value
value must be nonnegative- Construct a valid Reading before probing the static helper with bool True.
- Run two different invalid strings and read the distinct error messages.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Use `type(value) is int and value >= 0` in valid_value. In from_text, split once at ':', strip both parts, convert with int, validate the result, then return cls(name, value). Raise ValueError when a part is missing or invalid.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Hard-coding the base class
return Reading(name, value)return cls(name, value)What happens: SubReading.from_text returns a Reading instead of a SubReading.
The classmethod's cls parameter preserves the calling class.
Treating bool as a count
isinstance(value, int)type(value) is int and value >= 0What happens: True passes validation as the integer one.
An exact-type check rejects bool for this domain contract.