⚡IntermediateObject-Oriented Python

Properties, @staticmethod, @classmethod

Controlled attribute access with @property, shared utility with @staticmethod, and alternative constructors with @classmethod.

Overview

Python gives you three built-in method decorators for controlling how methods behave on a class. **@property** turns a method into a read-only attribute. Accessing `obj.temperature` calls the getter silently no parentheses. Add a setter with `@temperature.setter` to allow assignment. This is the Pythonic way to add validation or computation to attributes without breaking the `obj.attr` access interface. **@classmethod** receives the class (`cls`) as its first argument instead of the instance (`self`). It's used for alternative constructors factory methods that create instances from different input formats (e.g., `Date.from_string("2026-01-15")`). **@staticmethod** receives neither `self` nor `cls`. It's just a regular function that lives inside the class namespace for organisational reasons a utility function logically related to the class.

Key Points

  • •@property: makes a method callable as an attribute no parentheses, can add validation in setter
  • •@temperature.setter: defines the write path; @temperature.deleter: defines del behaviour
  • •@classmethod: receives cls used for factory/alternative constructors, class-level operations
  • •@staticmethod: receives neither self nor cls a utility function namespaced inside the class
  • •Properties hide implementation detail switch from plain attribute to computed value transparently
  • •Private by convention: prefix with _ (e.g. self._radius) to signal "internal use"

Examples

@property with getter and setter

class Circle:
    def __init__(self, radius):
        self._radius = radius   # private by convention

    @property
    def radius(self):           # getter accessed as obj.radius
        return self._radius

    @radius.setter
    def radius(self, value):    # setter called on obj.radius = x
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self):             # read-only computed property
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
print(c.radius)       # 5
print(f"{c.area:.2f}") # 78.54
c.radius = 10
print(c.radius)       # 10
# c.radius = -1       # raises ValueError

@classmethod as alternative constructor

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_str):   # cls is the Date class itself
        year, month, day = map(int, date_str.split("-"))
        return cls(year, month, day)  # equivalent to Date(year, month, day)

    @classmethod
    def today(cls):
        from datetime import date
        d = date.today()
        return cls(d.year, d.month, d.day)

    def __str__(self):
        return f"{self.year}-{self.month:02d}-{self.day:02d}"

d1 = Date(2026, 1, 15)
d2 = Date.from_string("2026-06-20")
d3 = Date.today()
print(d1)   # 2026-01-15
print(d2)   # 2026-06-20

@staticmethod for utility functions

class TemperatureConverter:
    @staticmethod
    def celsius_to_fahrenheit(c):
        return c * 9 / 5 + 32

    @staticmethod
    def fahrenheit_to_celsius(f):
        return (f - 32) * 5 / 9

    @staticmethod
    def is_valid_celsius(c):
        return -273.15 <= c <= 1_000_000

# Call directly on the class no instance needed
print(TemperatureConverter.celsius_to_fahrenheit(100))  # 212.0
print(TemperatureConverter.fahrenheit_to_celsius(32))   # 0.0
print(TemperatureConverter.is_valid_celsius(-300))      # False

staticmethod vs classmethod: use staticmethod when you don't need cls or self; use classmethod when you need to create instances or access class-level data.

Common Mistakes

  • Forgetting the @property decorator and calling the method with () or vice versa, adding () to a property
  • Defining a setter without a getter first @prop.setter requires @property to be defined above it
  • Using @staticmethod when you should use @classmethod if the function needs to create an instance, it needs cls

Note

Properties are especially useful for adding validation after the fact. If you start with a plain attribute and later need to validate it, change it to a property with a setter all existing code that reads or writes `obj.attr` continues to work unchanged.

Exercises

Write Python code to solve each exercise. Run it in your browser and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the main benefit of using @property over a plain attribute?