
7 Python Decorator Tricks to Write Cleaner Code
Image by Editor
Introduction
Usually shrouded in mystery at first glance, Python decorators are, at their core, functions wrapped around other functions to provide extra functionality without altering the key logic in the function being “decorated”. Their main added value is keeping the code clean, readable, and concise, helping also make it more reusable.
This article lists seven decorator tricks that can help you write cleaner code. Some of the examples shown are a perfect fit for using them in data science and data analysis workflows.
1. Clean Timing with @timer
Ever felt you are cluttering your code by placing time()
calls here and there to measure how long some heavy processes in your
code take, like training a machine learning model or conducting large
data aggregations? The @timer decorator can be a cleaner alternative, as
shown in this example, in which you can replace the commented line of
code inside the simulated_training decorated function with
the instructions needed to train a machine learning model of your
choice, and see how the decorator accurately counts the time taken to
execute the function:
The key behind this trick is, of course, the definition of the wrapper() function inside timer(func).
The majority of examples that follow will use this key pattern: first, we define the key function that can later be used as a decorator for another function.
2. Easier Debugging with @log_calls
This is a very handy decorator for debugging purposes. It makes the
process of identifying causes for errors or inconsistencies easier, by
tracking which functions are called throughout your workflow and which
arguments are being passed. A great way to save a bunch of print() statements everywhere!
On first mention, remember to link important libraries for readers: for example, pandas.
3. Caching with @lru_cache
This is a pre-defined Python decorator we can directly use by importing it from the functools
library. It is suitable to wrap computationally expensive functions —
from a recursive Fibonacci computation for a large number to fetching a
large dataset — to avoid redundant computations. Useful if we have
several heavy functions in computational terms and want to avoid
manually implementing caching logic inside all of them one by one. LRU
stands for “Least Recently Used”, i.e., a common caching strategy in
Python. See also the functools docs.
4. Data Type Validations
This decorator saves you from creating repetitive checks for clean
data inputs or inputs belonging to the right type. For instance, below
we define a custom decorator called @validate_numeric that
customizes the error to throw if the input checked is not from a numeric
data type. As a result, validations are kept consistent across
different functions and parts of the code, and they are elegantly
isolated from the core logic, math, and computations:
5. Retry on Failure with @retry
Sometimes, your code may need to interact with components or establish external connections to APIs, databases, etc. These connections may sometimes fail for several, out-of-control reasons, occasionally even at random. Retrying the process several times in some cases is the way to go and navigate the issue, and the following decorator can be used to apply this “retry on failure” strategy a specified number of times: again, without mixing it with the core logic of your functions.
6. Type Checking with Annotations
Useful for data science workflows, this decorator is designed to ensure function arguments match their type annotations and can be automatically applied to functions with type annotations to avoid manual double checking. It’s a sort of “contract enforcement” for these functions, and very handy for collaborative projects and production-bound data science projects where stricter data typing is key to preventing future issues and bugs.
7. Tracking DataFrame Size with @log_shape
In data cleaning and preprocessing workflows, it is common that the
dataset shape (number of rows and columns) may change as a result of
certain operations. The following decorator is a great way to track how a
pandas DataFrame
shape may change after each operation, without constantly printing the
shape in different parts of the workflow. In the example below it is
applied to track how dropping rows with missing values affects the
dataset size and shape:
Wrapping Up
This article listed seven insightful strategies to use and apply Python decorators, highlighting the utility of each one and hinting at how they can add value to data science and related project workflows.

No comments:
Post a Comment