Python’s power and flexibility often come from its ability to modify and extend functions without changing their actual code. One of the most elegant features that enable this is decorators. While you can create your own custom decorators, Python also offers a set of built-in decorators that are ready to use and solve common programming tasks elegantly.
In this lesson, we will explore the most important built-in decorators in Python, understand their practical uses, and see how they can help you write cleaner, more readable, and more efficient code.
What Are Decorators?
Before diving into the built-in decorators, let’s recap what decorators are.
A decorator is essentially a function that takes another function (or method) as input, modifies or enhances it, and returns it—usually without modifying the original function’s source code. This allows you to add functionality before or after the original function runs, or even replace it entirely.
Decorators are applied using the @decorator_name syntax, placed immediately above the function definition.
💡 Why Use Decorators?
Decorators help you follow the DRY principle (Don’t Repeat Yourself) by abstracting repetitive tasks such as logging, access control, or caching, letting you keep your core logic clean and focused.
Python’s Key Built-in Decorators
Python includes three primary built-in decorators that you'll use frequently, especially when working with classes:
@staticmethod@classmethod@property
Each of these serves a different purpose, mostly related to object-oriented programming. Let’s break down each one with practical examples.
@staticmethod: Methods Without Instance Dependency
A staticmethod is a method inside a class that does not depend on the instance (or the class) at all. It behaves like a regular function but lives in the class’s namespace, making your code more organized.
Because it doesn’t take self or cls parameters, it cannot access instance attributes or class attributes.
📌 Deep Dive: Using @staticmethod
class MathHelper:
@staticmethod
def add(x, y):
return x + y
# Using the static method without creating an instance
result = MathHelper.add(5, 7)
print(result) # Output: 12
Notice how add is called directly on the class without instantiating it. This is ideal for utility functions related to the class’s purpose but not dependent on instance state.
@classmethod: Working with the Class Itself
A classmethod is similar to a static method but receives the class itself as the first argument, conventionally named cls. This allows the method to access and modify class state that applies across all instances.
One common use case is alternative constructors or factory methods.
📌 Deep Dive: Using @classmethod
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
@classmethod
def get_population(cls):
return cls.population
@classmethod
def from_birth_year(cls, name, birth_year):
from datetime import date
age = date.today().year - birth_year
person = cls(name)
person.age = age
return person
p1 = Person("Alice")
p2 = Person.from_birth_year("Bob", 1990)
print(Person.get_population()) # Output: 2
print(p2.age) # Output: (current year - 1990)
33
In this example, from_birth_year acts as a factory method, constructing a Person instance while adding an additional attribute age. The get_population class method accesses the class attribute population, counting how many Person instances were created.
@property: Creating Managed Attributes
The @property decorator allows you to define a method that behaves like an attribute. It’s a way to implement getter logic without changing how users access the attribute.
This is incredibly useful for computed properties, validation, or lazy evaluation without altering the external interface of your class.
📌 Deep Dive: Using @property
class Circle:
def __init__(self, radius):
self._radius = radius # underscore prefix to indicate "private"
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return math.pi * (self._radius ** 2)
c = Circle(5)
print(c.radius) # Output: 5
print(f"Area: {c.area:.2f}") # Output: Area: 78.54
c.radius = 10
print(f"New area: {c.area:.2f}") # Output: New area: 314.16
# c.radius = -3 # This will raise ValueError
Area: 78.54
New area: 314.16
Here, radius is a property with both a getter and a setter, controlling access and validation. The area property computes the circle’s area dynamically whenever accessed.
💡 Tip: Use @property to provide a clean API while keeping internal data encapsulated and validated.
Comparison of Built-in Decorators
To summarize the key differences and use cases, here’s a handy comparison:
| Decorator | Purpose | Receives | Typical Use Case |
|---|---|---|---|
@staticmethod |
Defines method that does not access instance or class. | No implicit first argument. | Utility functions related to the class. |
@classmethod |
Defines method that works on the class. | Class (cls) as first argument. |
Alternative constructors, class-level logic. |
@property |
Creates managed attribute with getter/setter. | Instance (self) as first argument. |
Computed attributes, validation, encapsulation. |
What About Other Built-in Decorators?
While the three above are the most common, Python also includes some other built-in decorators useful in specific scenarios:
@functools.lru_cache: Caches the results of expensive function calls to speed up repeated calls.@contextlib.contextmanager: Simplifies the creation of context managers (used withwithstatements).@abc.abstractmethod: Marks methods as abstract in abstract base classes, requiring subclasses to override them.
These decorators come from Python’s standard library modules, and while not "built-in" in the strictest sense, they are integral to Pythonic design patterns.

Best Practices When Using Built-in Decorators
- Use
@staticmethodfor utility functions that logically belong to the class but don’t need instance or class data. - Use
@classmethodfor factory methods or when you need to affect or access class-level data. - Use
@propertyto hide implementation details and expose simple attribute-like interfaces with optional validation. - Don’t overuse decorators—only apply where they make your code more readable and maintainable.
- Remember the differences between these decorators to avoid confusing bugs, especially with method signatures.
⚠️ Common Pitfall
Trying to access instance variables inside a @staticmethod will fail since it does not receive self. If your method needs instance data, use a regular method or @property.
Summary
Built-in decorators are essential tools in Python that help you:
- Organize code with
@staticmethodfor independent methods. - Work with class state and create alternative constructors using
@classmethod. - Provide clean, controlled access to attributes with
@property, enabling encapsulation and validation.
Mastering these decorators unlocks a more Pythonic and elegant coding style, especially when designing classes and APIs.
Try experimenting with these decorators in your own projects to see how they simplify method management and data encapsulation!
Quick Knowledge Check
Test what you just learned
Question 1 of 2
Which built-in decorator would you use to create a method that can access and modify class-level data but not instance-level data?
Question 2 of 2
What happens if you try to access an instance attribute inside a method decorated with @staticmethod?
Loading results...