Properties, Class & Static Methods
Properties let you run code when an attribute is read or written, while keeping simple obj.attr syntax. Class methods work on the class itself — the standard way to write alternative constructors. Static methods are plain functions that live inside a class for organisation.
Properties: computed attributes
class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property def area(self): return self.width * self.height r = Rectangle(3, 4) print(r.area) r.width = 10 print(r.area)
12 40
@property turns a method into a read-only attribute — accessed without parentheses and always up to date.
Validating with a setter
class Thermostat: def __init__(self, celsius=20): self.celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if not 5 <= value <= 30: raise ValueError(f"{value}°C is out of range (5–30)") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 t = Thermostat() t.celsius = 25 print(t.celsius, t.fahrenheit) try: t.celsius = 99 except ValueError as err: print(err)
25 77.0 99°C is out of range (5–30)
The real value lives in _celsius; the property controls access. Note that __init__ assigns through the property too, so invalid starting values are rejected as well.
A great benefit: you can start with a plain attribute and later turn it into a property with validation, without changing any code that uses the class.
Read-only attributes
class Order: def __init__(self, order_id): self._id = order_id @property def id(self): return self._id o = Order(42) print(o.id) o.id = 7
42
Traceback (most recent call last):
File "main.py", line 11, in <module>
o.id = 7
^^^^
AttributeError: property 'id' of 'Order' object has no setterClass methods: alternative constructors
A @classmethod receives the class (conventionally cls) instead of an instance. Its main use is creating objects in different ways:
from datetime import date class Person: def __init__(self, name, birth_year): self.name = name self.birth_year = birth_year @classmethod def from_string(cls, text): name, year = text.split(",") return cls(name.strip(), int(year)) @classmethod def from_age(cls, name, age, today=date(2026, 10, 6)): return cls(name, today.year - age) def __repr__(self): return f"Person({self.name!r}, {self.birth_year})" print(Person.from_string("Ada, 1815")) print(Person.from_age("Ben", 30))
Person('Ada', 1815)
Person('Ben', 1996)Using cls(...) instead of Person(...) means subclasses get instances of the subclass. You've seen this pattern in the standard library: dict.fromkeys(), datetime.fromisoformat(), int.from_bytes().
Class methods for shared state
class Ticket: issued = 0 def __init__(self): Ticket.issued += 1 self.number = Ticket.issued @classmethod def reset(cls): cls.issued = 0 a, b, c = Ticket(), Ticket(), Ticket() print(c.number, Ticket.issued) Ticket.reset() print(Ticket().number)
3 3 1
Static methods
A @staticmethod receives neither the instance nor the class. It's just a function grouped with the class because it's related:
class Temperature: def __init__(self, celsius): self.celsius = celsius @staticmethod def c_to_f(c): return c * 9 / 5 + 32 def describe(self): return f"{self.celsius}°C = {self.c_to_f(self.celsius)}°F" print(Temperature.c_to_f(100)) print(Temperature(37).describe())
212.0 37°C = 98.6°F
In Python, a plain module-level function is often just as good. Use a static method when the function clearly belongs to the class's concept.
Which kind of method?
| Kind | First parameter | Use for |
|---|---|---|
| Instance method | self (the object) | Behaviour that uses or changes an object's data — most methods |
@classmethod | cls (the class) | Alternative constructors, class-wide state |
@staticmethod | none | Helpers related to the class that need no object or class data |
@property | self | Computed or validated attributes |