Python Module 9 – Object-oriented Python and the data model
Methods: instance, class, static and properties
Write alternative constructors with classmethod, helpers with staticmethod and checked attributes with property, and see what each kind receives.
What you will learn
- Explain what an instance method, a class method, a static method and a property each receive
- Write alternative constructors with @classmethod that also build subclasses
- Decide when a @staticmethod or a module-level function fits better
- Validate attributes with a @property setter that the constructor uses too
- Choose between a property and functools.cached_property for a computed value
Before you start
What you’ll learn · Before you start
What you will learn
- Explain what an instance method, a class method, a static method and a property each receive
- Write alternative constructors with @classmethod that also build subclasses
- Decide when a @staticmethod or a module-level function fits better
- Validate attributes with a @property setter that the constructor uses too
- Choose between a property and functools.cached_property for a computed value
Before you start
On this page
A Day class that can only be built from three numbers makes every caller with the text "2026-08-15" split and
convert it first, and a Temperature whose celsius is a plain attribute accepts -500 without a word. Python’s
four kinds of method solve both, and what tells them apart is what each one receives. An instance method, the
default, receives the instance it was called on as self. A class method: A method marked with @classmethod. It receives the class the call went through as its first argument, cls, whether it is called through the class or through an instance., marked with
@classmethod, receives the class as cls, which makes it the natural home of
alternative constructor: A class method that builds an instance from another form of the data, such as a string or a row, by returning cls(...). date.fromisoformat() and dict.fromkeys() are alternative constructors. such as Day.from_iso(text); because cls is
whichever class the call went through, the same constructor also builds subclasses.
A static method: A function kept in a class with @staticmethod. A call through the class or through an instance passes it no instance and no class, only the arguments written., marked with @staticmethod, receives nothing extra: it is a plain
function kept in the class. A property: An attribute whose reads and assignments run methods: a getter and, optionally, a setter. @property turns a method into the getter, and callers keep writing obj.name., marked with @property, runs a method when code
reads or assigns an attribute, so t.celsius can be computed or checked while callers keep the plain attribute
syntax. Below you will compare the four and watch a class method and a property at work.
Each kind of method receives something different
Every method is written the same way, with def in the class body; the decorator above it decides what a call passes
in:
What a call passes to each kind of method
Text description of the diagram
The diagram has four boxes, one for each kind, and each box shows a call at the top and, below it, what the method actually receives.
- Instance method: the call t.show(5) runs show(t, 5); the instance t arrives as self.
- Class method: the call Day.from_iso(s) runs from_iso(Day, s); the class arrives as cls. Called through an instance, it receives the instance's class.
- Static method: the call Day.is_valid(y, m, d) runs is_valid(y, m, d); nothing is added to the arguments.
- Property: the assignment t.celsius = 30 runs the property's setter as setter(t, 30); reading t.celsius runs its getter with t.
The decorators work because the class stores a different object for each kind, and the attribute lookup of the previous lesson asks that object what to hand back. A function comes back bound to the instance, a class method comes back bound to the class, a static method comes back as the bare function, and a property calls its getter. The table puts the four side by side:
| Kind | Receives | Use it for |
|---|---|---|
| Instance method | the instance, as self |
work on one object’s data |
| Class method | the class, as cls |
alternative constructors |
| Static method | only its own arguments | a helper that belongs with the class |
| Property | the instance, as self, when the attribute is read or assigned |
computed or checked attributes |
Start with an instance method
Default to an instance method; use a class method to build objects, a static method for a helper that needs neither
the object nor the class, and a property to keep the obj.name syntax.
Checkpoint
Check: What each kind of method receives
A class method builds objects of the class it is called on
__init__ gives a class one way to be built. A class method adds more, each one a recipe that turns another form of
the data, such as a string, a dictionary or a row of a file, into the arguments of the normal constructor:
import calendar
class Day:
def __init__(self, year, month, day):
if not self.is_valid(year, month, day):
raise ValueError(f"not a day of the calendar: {year}-{month}-{day}")
self.year, self.month, self.day = year, month, day
@classmethod
def from_iso(cls, text):
"""Build a Day, or an instance of a subclass, from 'YYYY-MM-DD'."""
year, month, day = (int(part) for part in text.split("-"))
return cls(year, month, day)
@staticmethod
def is_valid(year, month, day):
return 1 <= month <= 12 and 1 <= day <= calendar.monthrange(year, month)[1]
def iso(self):
return f"{self.year:04}-{self.month:02}-{self.day:02}"
class Holiday(Day): # a subclass: it inherits everything above
pass
d = Day.from_iso("2026-03-15")
h = Holiday.from_iso("2026-08-15")
print(type(d).__name__, d.iso())
print(type(h).__name__, h.iso()) # cls was Holiday, so a Holiday came back
print(Day.is_valid(2026, 2, 29), d.is_valid(2028, 2, 29))
try:
Day.from_iso("2026-02-30")
except ValueError as error:
print("ValueError:", error) Output
Day 2026-03-15 Holiday 2026-08-15 False True ValueError: not a day of the calendar: 2026-2-30
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 day_constructors.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
from_isosplits the text, converts the three parts and returnscls(year, month, day), so__init__still checks every new object, including the impossible day in the last line.- Called on the subclass,
Holiday.from_iso(...)receivedHolidayasclsand returned aHoliday. Subclasses get the constructor without writing anything; the lesson on inheritance shows how they extend it. - The standard library uses the same pattern:
dict.fromkeys(),int.from_bytes()anddatetime.date.fromisoformat()are all class methods.
Step through the call on the subclass and watch what cls is:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The call. Holiday has no from_iso of its own, so the lookup finds the class method that Day defines, as it finds any inherited method.
- cls is Holiday. A class method receives the class the call went through, so cls is Holiday, not Day, although the method is written in Day.
- Text to numbers. The method splits the text at each hyphen and converts the three parts with int(), giving the year 2026, the month 8 and the day 15.
- cls(2026, 8, 15). return cls(year, month, day) calls Holiday(2026, 8, 15), the normal constructor, so __init__ runs on a new Holiday.
- The check passes. __init__ asks is_valid(2026, 8, 15), which is True because August has 31 days, and stores the three numbers on the new object.
- A Holiday comes back. The caller receives a Holiday, as the second line of output shows. The last call takes the same path with a day that its month does not have, and there the check raises ValueError.
Checkpoint
Check: Class methods as constructors
A static method is a plain function kept in the class
is_valid is a static method: it needs neither an instance nor the class, only three numbers, and
calendar.monthrange() supplies the length of the month, leap years included. It can be called through the class or
through an instance, as the third line of output shows: Day.is_valid(2026, 2, 29) gives False, because 2026 is not
a leap year, and d.is_valid(2028, 2, 29) gives True. Step through both calls and watch what each one receives:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Through the class. Day.is_valid(2026, 2, 29) calls the function with exactly the three numbers written. A static method is given no instance and no class.
- How long is the month?. is_valid asks calendar.monthrange(2026, 2) how many days month 2 of the year 2026 has. 2026 is not a leap year, so the answer is 28.
- 29 is too many. Day 29 is past the 28 days of the month, so is_valid returns False, the first value of the third line of output.
- Through an instance. d.is_valid(2028, 2, 29) is called through the Day object d, yet the function again receives only the three numbers. Python puts nothing in front of them, neither d nor its class.
- A leap year. 2028 is a leap year, so monthrange(2028, 2) gives 29 days and day 29 exists.
- True through the instance. is_valid returns True, the second value of the third line. Both calls ran the same plain function on the arguments written, nothing more.
Neither call added an argument. A static method is just a function in the class’s namespace, so a module-level function is often as clear. Keep it in the class when it is part of what the class offers, such as a check that callers may want to run before they build a day.
Checkpoint
Check: Static methods
A property runs a method when an attribute is read or assigned
A property lets a class check or compute a value while its users keep writing t.celsius, as if it were a plain
attribute:
class Temperature:
ABSOLUTE_ZERO = -273.15
def __init__(self, celsius):
self.celsius = celsius # an assignment, so it runs the setter below
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < Temperature.ABSOLUTE_ZERO:
raise ValueError(f"{value} °C is below absolute zero")
self._celsius = float(value)
@property
def fahrenheit(self): # no setter: read-only, computed on every read
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.celsius, t.fahrenheit)
t.celsius = -40
print(t.celsius, t.fahrenheit, vars(t))
for attempt in (lambda: setattr(t, "celsius", -300), lambda: Temperature(-500)):
try:
attempt()
except ValueError as error:
print("ValueError:", error)
try:
t.fahrenheit = 0
except AttributeError as error:
print("AttributeError:", error)
print(t.celsius) # the refused values changed nothing Output
25.0 77.0
-40.0 -40.0 {'_celsius': -40.0}
ValueError: -300 °C is below absolute zero
ValueError: -500 °C is below absolute zero
AttributeError: property 'fahrenheit' of 'Temperature' object has no setter
-40.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 temperature.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
@propertymakes the firstcelsiusmethod the getter: readingt.celsiuscalls it.@celsius.setteradds the setter: The method of a property that runs when the attribute is assigned, as in t.celsius = 30. It can check the value before storing it, usually under another name such as _celsius., whicht.celsius = -40calls with the new value. The setter stores the value under another name,_celsius, because the namecelsiusbelongs to the property.__init__assignsself.celsius, notself._celsius, so the constructor goes through the same check:Temperature(-500)was refused like the assignment of -300, and neither changed the stored value.fahrenheithas no setter, so it is read-only and computed from_celsiuson every read; assigning it raisesAttributeError.
Step through the two assignments and the constructor call of the program:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- An assignment. t.celsius = -40 stores nothing by itself. Because celsius is a property, the assignment calls its setter with the value -40.
- The check passes. The setter compares -40 with absolute zero, -273.15. The value is above it, so the setter goes on.
- Stored under _celsius. The setter stores float(value) under another name, _celsius, because the name celsius belongs to the property. That is the {'_celsius': -40.0} that vars(t) printed.
- An impossible value. t.celsius = -300 calls the same setter with -300.
- Refused. -300 is below -273.15, so the setter raises ValueError before it stores anything. The dictionary still holds -40.0, which the last line of output shows.
- The constructor too. __init__ assigns self.celsius, not self._celsius, so building Temperature(-500) goes through the same setter and is refused in the same way.
The biggest benefit is one that callers never see. A class can start with a plain celsius attribute and turn it into
a property on the day it needs a check, and every line that reads or assigns t.celsius keeps working unchanged. That
is why Python classes do not write get_celsius() and set_celsius() methods in advance. For a value that is
expensive to compute and does not change, functools.cached_property computes it on the first read and keeps the
result in the instance.
Checkpoint
Check: Properties, getters and setters
Check yourself
Practice that adapts to you
Practise this lesson
11 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
How each kind works inside, with three bugs reproduced Pro: opens with a Pro, Premium or Ultimate pass
Each kind of method is a different object stored in the class, and the attribute lookup asks that object what to hand back. This part opens the class’s dictionary, binds each kind by hand, and reproduces the three bugs that these mechanics cause most often.
In this part:
- What the class stores, and what each lookup returns
- Three bugs reproduced: the wrong type, a skipped check and endless recursion
1 diagram · 2 runnable examples (Python) · about 7 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
A property or a cached_property: the work counted, a stale value and a race Pro: opens with a Pro, Premium or Ultimate pass
A property runs its getter on every read, while a cached property runs it once and keeps the answer. That trade decides both the speed and the correctness of a computed attribute, so this part counts the work each one does, makes a cache go stale, reproduces a race between two threads, and ends with an exercise.
In this part:
- Counting the work: 1,000 reads, 1,000 runs against one
- A race reproduced: two threads, one instance, two runs of the getter
- Which computed attribute to choose, with an exercise
- A rectangle with checked sides and an alternative constructor
- Five rules for methods and computed attributes
1 diagram · 4 runnable examples (Python) · 1 exercise with sample tests · about 14 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher · Warm-up
What is the difference between @classmethod and @staticmethod?
A class method receives the class as its first argument, usually named cls, whether it is called through the class or through an instance. A static method receives no implicit argument at all: it is a plain function kept in the class's namespace. Class methods are the tool for alternative constructors such as Day.from_iso(text): they return cls(...), so called on a subclass they build the subclass. Static methods suit helpers that belong with the class but need neither an instance nor the class, such as a check of three numbers before a day is built; a function at module level is often just as clear. Both can be called through the class or through an instance. A method without either decorator is an instance method and receives the instance as self.
-
Fresher to mid
PriorityOrder.from_csv_row(row) returns plain Order objects, so every priority order goes to the slow queue, and nothing raised an error. The constructor is a class method of Order. What is the likely bug, how do you prove it, and how do you prevent it in other constructors?
-
Mid to senior
A widely used Product class has a public attribute price that about 40 places read and assign. You now have to reject negative prices and store prices as whole numbers of the smallest currency unit. How do you change the class without breaking its callers?
-
Mid to senior
A Dashboard object computes an expensive summary with functools.cached_property. After the nightly data refresh, users see old numbers, and under load the summary query sometimes runs twice for one dashboard. Explain both problems and design a fix.
Key takeaways
- An instance method receives the instance, a class method the class, a static method nothing extra, and a property runs a method when its attribute is read or assigned.
- Write alternative constructors as class methods that return
cls(...), so that subclasses get instances of themselves. - A static method is a plain function in the class’s namespace: keep it for helpers that belong to the class’s interface.
- A property’s setter can refuse bad values; assign the public name in
__init__so that construction is checked too. - A property can replace a plain attribute later without changing a single caller.
Terms in this lesson
- alternative constructor
- A class method that builds an instance from another form of the data, such as a string or a row, by returning cls(...). date.fromisoformat() and dict.fromkeys() are alternative constructors.
- class method
- A method marked with @classmethod. It receives the class the call went through as its first argument, cls, whether it is called through the class or through an instance.
- property
- An attribute whose reads and assignments run methods: a getter and, optionally, a setter. @property turns a method into the getter, and callers keep writing obj.name.
- setter
- The method of a property that runs when the attribute is assigned, as in t.celsius = 30. It can check the value before storing it, usually under another name such as _celsius.
- static method
- A function kept in a class with @staticmethod. A call through the class or through an instance passes it no instance and no class, only the arguments written.
References
- Built-in Functions: classmethod() (Python Software Foundation)
- Built-in Functions: staticmethod() (Python Software Foundation)
- Built-in Functions: property (Python Software Foundation)
- functools.cached_property() (Python Software Foundation)
- Descriptor Guide (Python Software Foundation)
- The Python Language Reference: invoking descriptors (Python Software Foundation)
- threading: Lock objects (Python Software Foundation)
- calendar.monthrange() (Python Software Foundation)
- datetime: date.fromisoformat() (Python Software Foundation)
- Programming FAQ: how do I cache method calls? (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress