Skip to main content

How to Use Method Chaining in Python

Method chaining is a programming technique that allows you to call multiple methods on an object in a single, continuous expression. Instead of storing intermediate results in temporary variables, each method call returns an object that the next method operates on - creating a fluent, readable pipeline of operations.

This pattern is widely used in Python libraries like Pandas, NumPy, Django ORM, and Flask, and it is equally powerful in your own custom classes. In this guide, you will learn how method chaining works, see practical examples across built-in types and popular libraries, and understand how to implement it in your own classes.

What Is Method Chaining?

Method chaining means calling multiple methods sequentially on the same object in a single line. Each method returns an object (often the same one), enabling the next method call in the chain.

Without method chaining

text = " hello, tutref! "
result = text.strip()
result = result.capitalize()
result = result.replace("tutref", "TutorialReference")
print(result)

Output:

Hello, TutorialReference!

With method chaining

text = " hello, tutref! "
result = text.strip().capitalize().replace("tutref", "TutorialReference")
print(result)

Output:

Hello, TutorialReference!
note

Both approaches produce the same result, but the chained version is more concise and expresses the entire transformation in a single, readable expression.

How Method Chaining Works

For method chaining to work, each method in the chain must return an object that supports the next method call. This object can be:

  • A new instance of the same type (as with immutable string methods).
  • The same object modified in place, returned via self (common in custom classes).
  • A different object entirely (e.g., split() returns a list from a string).

The chain breaks if any method returns None, because None has no methods to call.

Method Chaining With Built-In Strings

Python string methods are naturally chainable because they always return a new string:

text = "100"
result = text.zfill(10).replace('0', '9')
print(result)

Output:

9999999199

Step by step:

  1. "100".zfill(10)"0000000100" (padded with zeros to length 10)
  2. "0000000100".replace('0', '9')"9999999199" (all zeros replaced with nines)

Another example with multiple transformations:

text = " i love python! "
result = text.strip().title().split()
print(result)

Output:

['I', 'Love', 'Python!']

Common Mistake: Chaining Methods That Return None

Many list methods like append(), sort(), extend(), and reverse() modify the list in place and return None. Attempting to chain after these methods causes an AttributeError:

numbers = [3, 1, 2]

# ❌ sort() returns None: chain breaks
result = numbers.sort().copy()

Output:

AttributeError: 'NoneType' object has no attribute 'copy'

Fix: Only chain methods that return a value. Perform in-place operations separately:

# ✅ Correct: use sorted() which returns a new list
numbers = [3, 1, 2]
result = sorted(numbers).copy()
print(result) # [1, 2, 3]

Or use methods that do return objects:

numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, numbers)).copy()
print(result.index(6))

Output:

2
Methods that return None break the chain

Always check whether a method returns the modified object or None. In general:

  • String methods → return a new string ✅ (chainable)
  • List methods like append(), sort(), extend() → return None ❌ (not chainable)
  • Pandas methods → return a new DataFrame ✅ (chainable)
  • Custom class methods → chainable if they return self

Method Chaining in Pandas

Pandas is one of the best examples of method chaining in Python. Most DataFrame methods return a new DataFrame, making them naturally chainable:

import pandas as pd

df = pd.DataFrame({
'Name': ['Vijay', 'Rakesh', None, 'Gurleen'],
'Age': [24, 27, None, 22]
})

result = (
df
.dropna() # Remove rows with missing values
.sort_values(by='Age') # Sort by age ascending
.reset_index(drop=True) # Reset index after sorting
)

print(result)

Output:

      Name   Age
0 Gurleen 22.0
1 Vijay 24.0
2 Rakesh 27.0

The chain reads like a step-by-step recipe: "Take the DataFrame, drop nulls, sort by age, and reset the index."

Method Chaining in NumPy

While less common than in Pandas, NumPy arrays support chaining for many operations:

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

result = (
np.reshape(arr, (9,)) # Flatten to 1D
.astype(float) # Convert to float
.reshape((3, 3)) # Reshape back to 3x3
.transpose() # Swap rows and columns
.clip(3, 7) # Clamp values between 3 and 7
)

print(result)

Output:

[[3. 4. 7.]
[3. 5. 7.]
[3. 6. 7.]]

Implementing Method Chaining in Custom Classes

To make your own classes support method chaining, ensure that each method returns self:

class Calculator:
def __init__(self, value=0):
self.value = value

def add(self, n):
self.value += n
return self

def subtract(self, n):
self.value -= n
return self

def multiply(self, n):
self.value *= n
return self

def result(self):
return self.value


# Method chaining in action
answer = Calculator(10).add(5).multiply(2).subtract(3).result()
print(answer)

Output:

27

How it works: Each method modifies self.value and returns self, allowing the next method to be called on the same Calculator instance.

A More Practical Example: Shopping Cart

class ShoppingCart:
def __init__(self):
self.items = []

def add_item(self, item):
self.items.append(item)
return self

def remove_item(self, item):
if item in self.items:
self.items.remove(item)
return self

def checkout(self):
print(f"Checking out with {len(self.items)} item(s): {self.items}")
return self


cart = ShoppingCart()
cart.add_item('apple').add_item('banana').add_item('milk').remove_item('apple').checkout()

Output:

Checking out with 2 item(s): ['banana', 'milk']

A Query Builder Pattern

Method chaining is commonly used to build SQL-like queries:

class QueryBuilder:
def __init__(self, table):
self._table = table
self._conditions = []
self._order = None
self._limit = None

def where(self, condition):
self._conditions.append(condition)
return self

def order_by(self, column):
self._order = column
return self

def limit(self, n):
self._limit = n
return self

def build(self):
query = f"SELECT * FROM {self._table}"
if self._conditions:
query += " WHERE " + " AND ".join(self._conditions)
if self._order:
query += f" ORDER BY {self._order}"
if self._limit:
query += f" LIMIT {self._limit}"
return query


query = (
QueryBuilder("users")
.where("age > 18")
.where("active = 1")
.order_by("name")
.limit(10)
.build()
)

print(query)

Output:

SELECT * FROM users WHERE age > 18 AND active = 1 ORDER BY name LIMIT 10

Benefits and Drawbacks

AspectBenefitDrawback
ReadabilityFluent, natural flow for sequential operationsOverly long chains become hard to read
ConcisenessEliminates temporary variablesDebugging is harder - which method failed?
ExpressivenessChains read like step-by-step instructionsMethods with side effects can cause confusion
MaintenanceEasy to add/remove stepsRequires all methods to return self or an object

Best Practices

  1. Keep chains short - 3-5 methods per chain is a good rule of thumb. For longer chains, use parentheses and line breaks for clarity:
result = (
df
.dropna()
.sort_values(by='Age')
.reset_index(drop=True)
.head(10)
)
  1. Always return self in custom class methods intended for chaining.

  2. Don't chain methods that return None - check the documentation if unsure.

  3. Use intermediate variables for debugging - temporarily break the chain to inspect intermediate results:

# Break the chain for debugging
step1 = df.dropna()
print(step1.shape) # Inspect intermediate result
step2 = step1.sort_values(by='Age')
  1. Avoid mixing mutating and non-mutating methods in a single chain to prevent confusion about what state the object is in.

Conclusion

Method chaining is a powerful technique that makes Python code more concise, readable, and expressive by eliminating temporary variables and creating fluent operation pipelines. It works naturally with Python strings, Pandas DataFrames, and NumPy arrays, and you can implement it in your own classes by returning self from each method.

The key to effective method chaining is keeping chains reasonably short, only chaining methods that return useful objects, and breaking chains apart when debugging is needed.