Python Class Methods and Properties

Choose between instance, class and static methods, write alternative constructors and validate attributes with @property.

Lesson 37 of 107Advanced13 min read

A Python class can contain three kinds of method, distinguished by what they receive automatically. An instance method receives the object as self; a class method, marked @classmethod, receives the class as cls; a static method, marked @staticmethod, receives nothing extra. Separately, @property turns a method into something that looks like a plain attribute, so you can validate values when they are set.

Python
class Kiln:
    max_temp = 1300                    # class attribute

    def __init__(self, capacity):      # instance method
        self.capacity = capacity

    @classmethod
    def from_text(cls, text):          # class method
        return cls(int(text))

    @staticmethod
    def is_safe(temp):                 # static method
        return temp <= Kiln.max_temp

This lesson explains when to use each one, using a pottery studio managing its kilns. It builds on classes and objects and uses the decorator syntax from the decorators lesson.

Instance methods

An ordinary method defined inside a class is an instance method. Python passes the object it was called on as the first argument, which by convention is named self. Through self, the method reads and changes that object's attributes:

Python
class Kiln:
    def __init__(self, capacity):
        self.capacity = capacity
        self.loaded = 0

    def load(self, pieces):
        if self.loaded + pieces > self.capacity:
            raise ValueError("Kiln is full")
        self.loaded += pieces

small, large = Kiln(12), Kiln(40)
large.load(30)
print(small.loaded, large.loaded)   # 0 30

Each kiln keeps its own loaded count. Attributes set on self are per-instance state; attributes assigned in the class body are class state, shared by every instance. Mixing them up, for example keeping a list of loaded pieces as a class attribute, makes every kiln share the same list. Most methods you write will be instance methods.

Class methods

@classmethod makes the method receive the class as its first argument, conventionally cls. The most common use is an alternative constructor: another way to create an instance, from a different kind of input.

Python
class Kiln:
    def __init__(self, capacity):
        self.capacity = capacity

    @classmethod
    def from_spec(cls, spec):
        """Create a kiln from text such as 'capacity=24'."""
        _, value = spec.split("=")
        return cls(int(value))

kiln = Kiln.from_spec("capacity=24")

Writing cls(...) rather than Kiln(...) matters for inheritance: if GasKiln subclasses Kiln, then GasKiln.from_spec(...) builds a GasKiln. Hard-coding Kiln would always return the parent type. The standard library uses this pattern widely, for example datetime.fromisoformat() and dict.fromkeys(). Class methods can also read and update class attributes, such as a counter of how many kilns exist.

Static methods

@staticmethod gives a method no automatic first argument at all. It's a plain function that lives in the class's namespace because it's closely related to the class:

Python
class Kiln:
    @staticmethod
    def parse_capacity(text):
        value = int(text.strip())
        if value <= 0:
            raise ValueError("capacity must be positive")
        return value

print(Kiln.parse_capacity(" 24 "))   # 24

It can be called on the class or on an instance, and it can't see either unless you pass them in. Use it for helpers that belong with the class conceptually, such as unit conversions or validation rules, so callers find them in one place.

A module-level function is often just as good, and simpler to test and import. Python doesn't force everything into classes; choose a static method when keeping the helper beside the class genuinely makes the code easier to find and read.

Properties

@property lets a method be read like an attribute. Adding a .setter runs code whenever the attribute is assigned, which is the Pythonic way to validate values without changing how callers use the class:

Python
class Kiln:
    def __init__(self, capacity):
        self.capacity = capacity        # goes through the setter

    @property
    def capacity(self):
        return self._capacity

    @capacity.setter
    def capacity(self, value):
        if value < 0:
            raise ValueError("Capacity cannot be negative")
        self._capacity = value

The real value is stored under a different name, _capacity. If the setter wrote self.capacity = value, it would call itself again and again until RecursionError. A property with no setter is read-only: assigning to it raises AttributeError. Properties can also be computed: @property def free_space(self): return self.capacity - self.loaded always reflects the current state.

Python developers start with plain attributes and switch to a property only when validation or computation is needed. Because the syntax for callers stays kiln.capacity, that change doesn't break any existing code, which is why Python code rarely has Java-style get_capacity() and set_capacity() methods.

Independent practice and a worked check

Task: add a kiln alternative constructor and test valid and negative capacities.

Python
class Kiln:
    def __init__(self, capacity):
        self.capacity = capacity

    @classmethod
    def from_text(cls, text):
        return cls(int(text))

    @property
    def capacity(self):
        return self._capacity

    @capacity.setter
    def capacity(self, value):
        if value < 0:
            raise ValueError("Capacity cannot be negative")
        self._capacity = value

print(Kiln.from_text("18").capacity)      # 18
try:
    Kiln.from_text("-5")
except ValueError as error:
    print("Rejected:", error)             # Rejected: Capacity cannot be negative

The class method passes its value to cls, which runs __init__, which runs the setter, so validation applies however the kiln is created. For the worked check, the setter stores _capacity to avoid recursive setter calls: assigning self.capacity inside the setter would call the setter again.

Reference and next practice

The documentation covers classmethod, staticmethod and property. Continue with polymorphism and inner classes and magic methods. Practise with the Python exercises and Python quiz.

Class Methods and Properties Summary

  • Instance methods: An instance method receives self and operates on one object state.
  • Class methods: A classmethod receives cls and can act as an alternative constructor.
  • Static methods: A staticmethod has no automatic self or cls argument.
  • Properties: Properties combine attribute access with validation through getters and setters.

Class Methods and Properties Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Class Methods and Properties in a small program

Python
class Kiln:
    def __init__(self, capacity):
        self.capacity = capacity
    @property
    def capacity(self):
        return self._capacity
    @capacity.setter
    def capacity(self, value):
        if value < 0:
            raise ValueError("Capacity cannot be negative")
        self._capacity = value

print(Kiln(12).capacity)
Output
12
Line-by-line explanation
  1. Line 3Assigning self.capacity in __init__ runs the setter, so even the starting value is validated.
  2. Lines 4 to 6The getter returns the stored value.
  3. Lines 7 to 11The setter rejects negative values and stores valid ones in _capacity.
  4. Line 13Reading .capacity looks like a plain attribute but calls the getter.
Example 2

All three method types working together

Python
class Kiln:
    count = 0
    MAX_TEMP = 1300

    def __init__(self, name, capacity):
        self.name = name
        self.capacity = capacity
        self.loaded = 0
        Kiln.count += 1

    @classmethod
    def from_spec(cls, spec):
        name, capacity = spec.split(":")
        return cls(name.strip(), int(capacity))

    @staticmethod
    def is_safe(temp):
        return 0 < temp <= Kiln.MAX_TEMP

    @property
    def free_space(self):
        return self.capacity - self.loaded

    def load(self, pieces):
        if pieces > self.free_space:
            raise ValueError(f"{self.name} has room for {self.free_space}")
        self.loaded += pieces

electric = Kiln("Electric", 24)
gas = Kiln.from_spec("Gas: 40")
gas.load(15)
print(gas.name, gas.free_space)
print(Kiln.is_safe(1250), Kiln.is_safe(1400))
print("Kilns:", Kiln.count)
try:
    electric.load(30)
except ValueError as error:
    print(error)
Output
Gas 25
True False
Kilns: 2
Electric has room for 24
Line-by-line explanation
  1. Line 9Updating Kiln.count (not self.count) changes the shared class attribute.
  2. Lines 11 to 14The class method parses text and calls cls, returning a new kiln.
  3. Lines 20 to 22free_space is computed from current state every time it is read.

Common Class Methods and Properties Mistakes

Assigning the property inside its own setter

Don't
@capacity.setter
def capacity(self, value):
    self.capacity = value     # calls this setter again: RecursionError
Do
@capacity.setter
def capacity(self, value):
    self._capacity = value    # store under a different name

Hard-coding the class name in a class method

Don't
@classmethod
def from_spec(cls, spec):
    return Kiln(int(spec))    # subclasses get a plain Kiln
Do
@classmethod
def from_spec(cls, spec):
    return cls(int(spec))     # GasKiln.from_spec returns a GasKiln

Class Methods and Properties in Real Projects

These method types in real libraries

Alternative constructors are everywhere in the standard library and popular packages: datetime.fromtimestamp(), Path.home(), pd.DataFrame.from_records() and int.from_bytes() are all class methods. Properties appear wherever an attribute needs to be computed or protected, such as Path.suffix, datetime.year and model fields in many web frameworks.

In application code, a typical class has mostly instance methods, one or two class methods for creating instances from files, database rows or API responses, a property or two for derived values, and the occasional static helper. Dataclasses combine well with all three.

Class Methods and Properties FAQ

What is the difference between @classmethod and @staticmethod?

A class method receives the class as cls, so it can create instances and read class attributes, and it respects subclasses. A static method receives nothing automatically; it's an ordinary function grouped inside the class. Use @classmethod for alternative constructors and @staticmethod for related helpers that need no class or instance data.

When should I use @property in Python?

Use it when reading or setting an attribute needs logic: validation, a computed value or a read-only attribute. Start with a plain attribute; you can turn it into a property later without changing any code that uses it.

Why does my property setter cause RecursionError?

The setter assigns to the property's own name, self.capacity = value, which calls the setter again endlessly. Store the value under a different attribute, conventionally the same name with a leading underscore: self._capacity = value.

Can I call a class method on an instance?

Yes. kiln.from_text("18") works, and Python still passes the class, type(kiln), as cls. Calling it on the class, Kiln.from_text("18"), is clearer, since the method doesn't use the instance.

How do I make a read-only attribute in Python?

Define a @property without a setter. Reading works normally, and assigning raises AttributeError. Store the value in an underscore attribute that the property returns; the underscore signals that outside code shouldn't change it.

Class Methods and Properties Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

Why store _capacity in the property setter?

Multiple choicemedium

Which review concern is valid for class methods and properties?

Class Methods and Properties Quiz

2 questions to check what you learned.

  1. Why store _capacity in the property setter?

    • To enforce secrecy
    • To avoid recursive setter calls
    • Python ignores the distinction
  2. Which statement correctly describes static methods?

    • A staticmethod has no automatic self or cls argument.
    • Only the example output matters, not the rule
    • Every operation has the same behavior