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.
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_tempThis 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:
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 30Each 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.
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:
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 ")) # 24It 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:
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 = valueThe 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.
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 negativeThe 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.
Class Methods and Properties in a small program
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)12Line-by-line explanation
- Line 3Assigning
self.capacityin__init__runs the setter, so even the starting value is validated. - Lines 4 to 6The getter returns the stored value.
- Lines 7 to 11The setter rejects negative values and stores valid ones in
_capacity. - Line 13Reading
.capacitylooks like a plain attribute but calls the getter.
All three method types working together
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)Gas 25
True False
Kilns: 2
Electric has room for 24Line-by-line explanation
- Line 9Updating
Kiln.count(notself.count) changes the shared class attribute. - Lines 11 to 14The class method parses text and calls
cls, returning a new kiln. - Lines 20 to 22
free_spaceis computed from current state every time it is read.
Common Class Methods and Properties Mistakes
Assigning the property inside its own setter
@capacity.setter
def capacity(self, value):
self.capacity = value # calls this setter again: RecursionError@capacity.setter
def capacity(self, value):
self._capacity = value # store under a different nameHard-coding the class name in a class method
@classmethod
def from_spec(cls, spec):
return Kiln(int(spec)) # subclasses get a plain Kiln@classmethod
def from_spec(cls, spec):
return cls(int(spec)) # GasKiln.from_spec returns a GasKilnClass 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.
Why store _capacity in the property setter?
Which review concern is valid for class methods and properties?
Class Methods and Properties Quiz
2 questions to check what you learned.
Why store _capacity in the property setter?
- To enforce secrecy
- To avoid recursive setter calls
- Python ignores the distinction
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