Python methods and properties, explained
Python has three kinds of methods and a clean way to turn methods into attribute-like access.
Knowing when to use @classmethod, @staticmethod, and @property — and why Python
prefers properties over Java-style getters — is a common interview signal.
Three kinds of methods
The difference is what (if anything) gets passed as the first argument:
class Pizza:
DEFAULT_SIZE = "medium"
def __init__(self, toppings):
self.toppings = toppings
def describe(self): # instance method — gets self
return f"Pizza with {self.toppings}"
@classmethod
def margherita(cls): # classmethod — gets the class
return cls(["tomato", "mozzarella"])
@staticmethod
def is_valid_size(size): # staticmethod — gets nothing special
return size in {"small", "medium", "large"}
- An instance method receives
selfand works with object state. - A classmethod receives
cls(the class) and works with the class itself. - A staticmethod receives neither — it's a plain function namespaced under the class.
classmethod as an alternative constructor
The most common use of @classmethod is a named alternative constructor. Because it
receives cls, it works correctly with subclasses too:
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
@classmethod
def from_string(cls, s):
y, m, d = map(int, s.split("-"))
return cls(y, m, d) # cls, not Date — subclass-friendly
Date.from_string("2026-06-19")
Using cls(...) rather than Date(...) means a subclass's from_string returns the
subclass, not the base.
@property — methods that look like attributes
@property turns a method into a computed, read-only attribute accessed without parentheses:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
c = Circle(2)
c.area # 12.566... — no parentheses; computed on access
c.area = 10 # AttributeError — read-only by default
area is always derived from radius, so it can never go stale.
Adding a setter with validation
A property can have a setter, letting you run validation while keeping plain attribute syntax:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("below absolute zero")
self._celsius = value
t = Temperature(20)
t.celsius = 25 # runs the setter (validates)
t.celsius = -300 # ValueError
Why properties beat Java-style getters
In Java you write getX()/setX() from the start "in case" you need validation later. In
Python you just expose a plain attribute and upgrade it to a property later without
changing the public API:
# Start simple:
class User:
def __init__(self, name):
self.name = name # public attribute
# Later, add validation transparently — callers still write user.name:
class User:
def __init__(self, name):
self.name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("name required")
self._name = value
Callers never need to change from user.name to user.getName() — that's the payoff.
Recap
Instance methods take self and use object state; classmethods take cls and are
ideal for alternative constructors that stay subclass-friendly via cls(...);
staticmethods take nothing special and just live in the class namespace. @property
exposes computed or read-only attributes with plain obj.attr syntax, and a paired
@x.setter adds validation. Because of properties, Python code starts with public
attributes and adds getters/setters later without breaking callers — so you never write
Java-style boilerplate up front.