📝 Python

Function Composition

P
Author
Pyland
📅
Published
03.04.2026
⏱️
Reading time
3 min
👁️
Views
200
🌳
Level
Advanced

In math: (f ∘ g)(x) = f(g(x)) — first g, then f.

What Is Function Composition?

Function composition is the practice of combining simple functions into more complex ones.

In math: (f ∘ g)(x) = f(g(x)) — first g, then f.

def add_10(x):
    return x + 10

def multiply_2(x):
    return x * 2

# Manual composition: first add_10, then multiply_2
result = multiply_2(add_10(5))
print(result)  # 30
# 5 → add_10 → 15 → multiply_2 → 30

Why Use Composition?

Without composition the same pipeline is written out by hand every time:

data1 = step1(raw_data)
data2 = step2(data1)
result = step3(data2)

# For another dataset — again:
data1 = step1(other_data)
data2 = step2(data1)
result = step3(data2)

With composition — define the pipeline once, reuse it everywhere:

process = pipe(step1, step2, step3)

result1 = process(raw_data)
result2 = process(other_data)
result3 = process(more_data)

compose() and pipe()

compose() — right to left

def compose(*functions):
    """Apply functions right to left: f(g(h(x)))."""
    def composed(x):
        result = x
        for func in reversed(functions):
            result = func(result)
        return result
    return composed

def add_10(x):
    return x + 10

def multiply_2(x):
    return x * 2

def square(x):
    return x ** 2

# compose: the last argument runs first
pipeline = compose(square, multiply_2, add_10)
print(pipeline(5))  # 900
# 5 → add_10 → 15 → multiply_2 → 30 → square → 900

Reads right to left (mathematics convention).

pipe() — left to right

def pipe(*functions):
    """Apply functions left to right."""
    def piped(x):
        result = x
        for func in functions:
            result = func(result)
        return result
    return piped

# pipe: the first argument runs first
pipeline = pipe(add_10, multiply_2, square)
print(pipeline(5))  # 900
# 5 → add_10 → 15 → multiply_2 → 30 → square → 900

Reads left to right — more natural for code.


Practical Examples

Example 1: String cleaning

import string

def trim(text):
    return text.strip()

def lowercase(text):
    return text.lower()

def remove_punctuation(text):
    return text.translate(str.maketrans("", "", string.punctuation))

clean_text = pipe(trim, lowercase, remove_punctuation)

dirty = "  Hello, World!  "
print(clean_text(dirty))  # "hello world"

Example 2: Price calculation

def validate_positive(x):
    if x <= 0:
        raise ValueError("Must be positive")
    return x

def apply_discount(percent):
    def discount(price):
        return price * (1 - percent / 100)
    return discount

def add_tax(percent):
    def tax(price):
        return price * (1 + percent / 100)
    return tax

def round_price(price):
    return round(price, 2)

# Pipeline: validate → 20% discount → 10% tax → round
calculate_price = pipe(
    validate_positive,
    apply_discount(20),
    add_tax(10),
    round_price
)

print(calculate_price(100))  # 88.0
# 100 → validate → 80 → 88 → 88.0

Decorators Are Composition Too

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

def exclaim_decorator(func):
    def wrapper(*args, **kwargs):
        return f"{func(*args, **kwargs)}!!!"
    return wrapper

@exclaim_decorator
@uppercase_decorator
def greet(name):
    return f"hello, {name}"

print(greet("Alice"))  # "HELLO, ALICE!!!"
# greet → uppercase → exclaim

The decorator stack is applied bottom to top: uppercase_decorator first, then exclaim_decorator.


Common Mistakes

Mistake 1: Wrong argument order

def add_10(x):
    return x + 10

def multiply_2(x):
    return x * 2

# compose reads RIGHT TO LEFT — multiply_2 runs first!
wrong = compose(add_10, multiply_2)
print(wrong(5))  # 20  (5*2=10, 10+10=20)

# pipe reads LEFT TO RIGHT — add_10 runs first
right = pipe(add_10, multiply_2)
print(right(5))  # 30  ((5+10)*2=30)

Mistake 2: Incompatible function signatures

def add(a, b):    # takes 2 arguments
    return a + b

def square(x):    # takes 1 argument
    return x ** 2

# ❌ ERROR: square would receive a tuple instead of a number
# pipeline = pipe(add, square)

# ✅ CORRECT: fix one argument with partial
from functools import partial

add_10 = partial(add, 10)
pipeline = pipe(add_10, square)
print(pipeline(5))  # 225  ((5+10)^2)

Function composition is the foundation of functional style in Python. Use pipe() for readable transformation chains and compose() when mathematical ordering matters.

Your reaction to the article

💬 Comments (0)

🔐 Sign in to leave a comment
🚪 Login
💭

No comments yet

Be the first to share your opinion about this article!

🔗 Similar

Similar articles

Continue learning with these materials

📝

Event Loop in Python: How asyncio Achieves "Paral…

Event loop is the heart of asyncio. It doesn't run code in parallel across multiple...

📅 30.06.2026 👁️ 137
📝

pytest-django: Testing Django

Охватываемые темы: Installation, @pytest.mark.djangodb, Fixtures, Testing views.

📅 30.06.2026 👁️ 144
📝

pip: Python Package Manager

Охватываемые темы: Installing packages, Upgrading and removing, requirements.txt, Virtual environment.

📅 30.06.2026 👁️ 140

Did you like the article?

Subscribe to our updates and receive new articles first. Grow with PyLand!