Skip to content

Properties and Class Methods

@property turns a method into an attribute-style accessor. Callers use obj.celsius instead of obj.get_celsius(), keeping the public API clean while allowing validation logic to run behind the scenes.

class Temperature:
def __init__(self, celsius: float = 0.0) -> None:
self._celsius = celsius # underscore = internal storage
@property
def celsius(self) -> float:
return self._celsius

Pair @property with @name.setter to intercept assignment:

@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value

Now t.celsius = -300 raises ValueError automatically. Without a setter, the property is read-only — attempts to assign raise AttributeError.

A property can compute its value on the fly with no backing storage:

@property
def fahrenheit(self) -> float:
return self._celsius * 9 / 5 + 32

t.fahrenheit looks like a plain attribute to the caller, but it is recalculated each time.

A class method receives the class (cls) as its first argument instead of the instance. The most common use is providing alternative constructors:

@classmethod
def from_fahrenheit(cls, f: float) -> "Temperature":
return cls((f - 32) * 5 / 9)
boiling = Temperature.from_fahrenheit(212)
print(boiling.celsius) # 100.0

Subclasses automatically inherit the class method and cls will refer to the subclass — so the factory creates an instance of the correct type.

A static method receives neither the instance nor the class. Use it for utilities that logically belong to the class but do not need access to class or instance state:

@staticmethod
def absolute_zero() -> float:
return -273.15
print(Temperature.absolute_zero()) # -273.15
class Temperature:
def __init__(self, celsius: float = 0.0) -> None:
self._celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value
@property
def fahrenheit(self) -> float:
return self._celsius * 9 / 5 + 32
@classmethod
def from_fahrenheit(cls, f: float) -> "Temperature":
return cls((f - 32) * 5 / 9)
@staticmethod
def absolute_zero() -> float:
return -273.15
t = Temperature(100)
print(t.celsius)
print(t.fahrenheit)
t.celsius = 0
print(t.fahrenheit)
t2 = Temperature.from_fahrenheit(212)
print(t2.celsius)
print(Temperature.absolute_zero())
What does `@property` do to a method?
How do you make a `@property` writable?
What is the first parameter of a `@classmethod`?
When is `@staticmethod` the right choice?