-
Notifications
You must be signed in to change notification settings - Fork 0
Python Lambda
In Python, a lambda function is a small, anonymous (unnamed) function. While standard functions are defined using the def keyword, lambda functions are defined using the lambda keyword.
A lambda function can take any number of arguments, but can only have ONE expression.
lambda arguments: expressionLet's visualize the difference in how Python treats these under the hood.
flowchart TD
subgraph Standard Function
A[def keyword] --> B[Function Name]
B --> C[Arguments]
C --> D[Multiple Statements / Block]
D --> E[Return Statement]
end
subgraph Lambda Function
F[lambda keyword] --> G[Anonymous / No Name]
G --> H[Arguments]
H --> I[Single Expression]
I --> J[Implicit Return]
end
# Standard function
def add_standard(x, y):
return x + y
# Lambda function
add_lambda = lambda x, y: x + y
print(add_standard(5, 3)) # Output: 8
print(add_lambda(5, 3)) # Output: 8Note for Junaid: While assigning a lambda to a variable (like add_lambda) works, PEP 8 (Python's style guide) discourages it. Lambdas are meant to be used inline or passed as arguments.
Lambdas truly shine when paired with higher-order functions like map() and filter().
map() applies a given function to every item of an iterable (like a list or dict) and returns an iterator (a generator-like object) yielding the results.
Syntax: map(function, iterable)
filter() constructs an iterator from elements of an iterable for which a function returns True.
Syntax: filter(function, iterable)
flowchart LR
A[Raw Iterable] --> B{filter: Keep if True}
B -->|Discard| C[X]
B -->|Keep| D[Filtered Iterable]
D --> E[map: Transform each item]
E --> F[Final Transformed Iterable]
products = [
{"name": "Laptop", "price": 1200, "in_stock": True},
{"name": "Mouse", "price": 25, "in_stock": False},
{"name": "Keyboard", "price": 75, "in_stock": True},
{"name": "Monitor", "price": 300, "in_stock": True}
]
# 1. FILTER: Get only in-stock products
# Lambda checks the 'in_stock' key in the dictionary
in_stock_items = filter(lambda p: p["in_stock"], products)
# 2. MAP: Apply a 10% discount to the filtered items
# Lambda calculates the new price and returns a NEW dictionary
discounted_items = map(
lambda p: {"name": p["name"], "price": p["price"] * 0.90},
in_stock_items
)
# Because map and filter return iterators, we must cast to list to see the data
final_list = list(discounted_items)
for item in final_list:
print(item)
# Output:
# {'name': 'Laptop', 'price': 1080.0}
# {'name': 'Keyboard', 'price': 67.5}
# {'name': 'Monitor', 'price': 270.0}A generator is a function that returns an iterator using the yield keyword. It evaluates lazily, meaning it generates one item at a time, saving massive amounts of memory compared to creating a full list in memory.
Crucial Concept: In Python 3, map() and filter() actually return generators (iterators)!
We can create custom generator functions that utilize lambdas for internal logic, or we can yield lambda functions themselves.
# A generator that yields lambda functions based on a multiplier
def create_multipliers(n):
for i in range(1, n + 1):
# Yielding a lambda function that captures 'i'
# Note: We use a default argument x=i to avoid late-binding closure issues
yield lambda x, i=i: x * i
# Using the generator
multipliers_gen = create_multipliers(3)
for func in multipliers_gen:
print(func(10))
# Output: 10, 20, 30While map and filter are great, Pythonic developers often prefer Generator Expressions for readability.
numbers = [1, 2, 3, 4, 5]
# Map/Filter approach (Returns a generator)
gen_map = map(lambda x: x**2, filter(lambda x: x % 2 != 0, numbers))
# Generator Expression approach (More Pythonic, highly recommended)
gen_expr = (x**2 for x in numbers if x % 2 != 0)
# Both consume the same memory and yield the same results!
print(list(gen_map)) # [1, 9, 25]
print(list(gen_expr)) # [1, 9, 25]Let's combine everything: list, dict, map, filter, and custom generators to process a massive dataset.
Scenario: Junaid, you are building a backend for a logistics company. You receive a massive list of shipment dictionaries. You need to:
- Filter out delayed or lost shipments.
- Calculate the shipping cost based on weight and distance.
- Group the valid shipments by region using a generator to handle memory efficiently.
- Output a final dictionary mapping regions to lists of processed shipments.
import random
# 1. GENERATE MOCK DATA (List of Dicts)
regions = ["North", "South", "East", "West"]
statuses = ["delivered", "in_transit", "delayed", "lost"]
shipments = [
{
"id": f"SHIP-{i:04d}",
"region": random.choice(regions),
"weight_kg": round(random.uniform(1.0, 50.0), 2),
"distance_km": random.randint(100, 5000),
"status": random.choice(statuses)
}
for i in range(1000) # 1000 shipments
]
# 2. THE PROCESSING PIPELINE
# Step A: Filter out bad statuses using filter() and lambda
valid_shipments = filter(
lambda s: s["status"] in ["delivered", "in_transit"],
shipments
)
# Step B: Map to calculate costs and format data using map() and lambda
# Cost formula: Base $10 + ($0.05 per kg) + ($0.01 per km)
processed_shipments = map(
lambda s: {
"id": s["id"],
"region": s["region"],
"cost": round(10 + (s["weight_kg"] * 0.05) + (s["distance_km"] * 0.01), 2),
"status": s["status"]
},
valid_shipments
)
# Step C: Custom Generator to group by region
def group_by_region(shipment_iterator):
"""Generator that yields (region, list_of_shipments) tuples."""
region_dict = {}
for shipment in shipment_iterator:
region = shipment["region"]
if region not in region_dict:
region_dict[region] = []
region_dict[region].append(shipment)
# Yield the grouped data one region at a time (Memory efficient!)
for region, ship_list in region_dict.items():
yield region, ship_list
# Step D: Execute the pipeline
# We pass the map iterator directly into our custom generator
grouped_generator = group_by_region(processed_shipments)
# Convert the generator output into a final Dict of Lists
final_report = {region: ship_list for region, ship_list in grouped_generator}
# 3. RESULTS
print(f"Total Regions Processed: {len(final_report)}")
for region, ships in final_report.items():
total_revenue = sum(s["cost"] for s in ships)
print(f"Region: {region} | Valid Shipments: {len(ships)} | Total Revenue: ${total_revenue:,.2f}")-
Memory Efficiency:
filterandmapdon't create intermediate lists. They pass data one by one togroup_by_region. - Readability: The logic for filtering and mapping is isolated in single-line lambdas.
-
Scalability: If
shipmentshad 10 million rows, this script would still run without crashing your RAM, thanks to the generator pipeline.
-
Implicit Return: Lambdas automatically return the result of the expression. You cannot use
returninside a lambda. -
Single Expression: You cannot use statements like
if/elseblocks,forloops, or variable assignments inside a lambda. (You can use the ternary operatora if condition else b). -
Python 3 Iterators:
map()andfilter()return iterators. If you need to iterate over them twice, you must cast them to alist()first. -
PEP 8 Rule: Never assign a lambda to a variable name (e.g.,
add = lambda x, y: x + y). Usedef add(x, y): return x + yinstead. Lambdas are for throwaway, inline functions.
| Concept | Syntax / Example | Description |
|---|---|---|
| Lambda | lambda x, y: x + y |
Anonymous function, single expression. |
| Ternary in Lambda | lambda x: "Even" if x%2==0 else "Odd" |
Inline if/else inside a lambda. |
| Map | map(lambda x: x*2, my_list) |
Applies function to all items. Returns iterator. |
| Filter | filter(lambda x: x>0, my_list) |
Keeps items where function returns True. |
| Dict Map | {k: func(v) for k, v in my_dict.items()} |
Dict comprehension (often preferred over map for dicts). |
| Generator Func | def gen(): yield lambda x: x*2 |
Function that yields lambdas or uses yield. |
| Gen Expression | (x*2 for x in my_list if x>0) |
Inline generator. Replaces map/filter combos. |
Test your understanding, Junaid! (Answers are at the bottom).
Q1. What is the output of the following code?
x = [1, 2, 3]
y = map(lambda a: a**2, filter(lambda b: b%2!=0, x))
print(list(y))A) [1, 4, 9]
B) [1, 9]
C) [2]
D) Error
Q2. Which of the following is considered un-Pythonic according to PEP 8?
A) sorted(users, key=lambda u: u['age'])
B) square = lambda x: x * x
C) list(filter(lambda x: x > 0, nums))
D) max(data, key=lambda item: item['score'])
Q3. True or False: In Python 3, calling map() immediately computes the results and stores them in a new list in memory.
Q4. How do you write a lambda that takes a dictionary d and returns the value of key "a" if it exists, otherwise returns 0?
A) lambda d: d.get("a", 0)
B) lambda d: return d["a"] if "a" in d else 0
C) lambda d: d["a"] or 0
D) Both A and C
Q5. What is the primary advantage of using a Generator Expression (x for x in data) over a List Comprehension [x for x in data]?
A) It runs faster.
B) It uses significantly less memory.
C) It allows multiple expressions.
D) It automatically filters out None values.
Put your skills to the test! Try to solve these without looking at the solutions below.
You have a list of temperatures in Celsius: celsius = [0, 10, 20, 30, 40].
Use map() and a lambda to convert them to Fahrenheit.
(Formula: F = C * 9/5 + 32)
Given a dictionary of users and their ages:
users = {"Alice": 25, "Bob": 17, "Charlie": 30, "Diana": 15}
Use filter() and a lambda to find the names of users who are 18 or older. (Hint: filter the .items() or .keys()).
Write a generator function called power_sequence(base).
It should yield lambda functions. The first lambda should calculate base**1, the second base**2, up to base**5.
Iterate through the generator and print the result of calling each lambda with the number 2.
You have a list of dictionaries representing books:
books = [
{"title": "Dune", "genre": "Sci-Fi", "pages": 412, "rating": 4.8},
{"title": "1984", "genre": "Dystopian", "pages": 328, "rating": 4.7},
{"title": "The Hobbit", "genre": "Fantasy", "pages": 310, "rating": 4.9},
{"title": "Neuromancer", "genre": "Sci-Fi", "pages": 271, "rating": 4.2}
]Create a pipeline that:
- Filters for books with a rating >= 4.5.
- Maps the filtered books to a new dictionary format:
{"title": title, "category": genre, "is_long": True/False}(whereis_longis True if pages > 300). - Converts the final result into a list.
Write a generator function chunk_generator(iterable, chunk_size) that takes any iterable (like a massive list) and yields chunks (lists) of size chunk_size.
Then, use map and a lambda to apply this generator to a list of 100 numbers, yielding chunks of 15. Print the chunks.
Solution 1:
celsius = [0, 10, 20, 30, 40]
fahrenheit = list(map(lambda c: (c * 9/5) + 32, celsius))
print(fahrenheit) # [32.0, 50.0, 68.0, 86.0, 104.0]Solution 2:
users = {"Alice": 25, "Bob": 17, "Charlie": 30, "Diana": 15}
# Filter the keys based on the value in the dictionary
adults = list(filter(lambda name: users[name] >= 18, users.keys()))
print(adults) # ['Alice', 'Charlie']Solution 3:
def power_sequence(base):
for i in range(1, 6):
# Yield a lambda. We use default arg i=i to capture the current loop value
yield lambda x, i=i: x ** i
gen = power_sequence(3)
for func in gen:
print(func(2))
# 2**1 = 2
# 2**2 = 4
# 2**3 = 8
# 2**4 = 16
# 2**5 = 32Solution 4:
books = [ ... ] # (Assume books list is defined)
# 1. Filter
high_rated = filter(lambda b: b["rating"] >= 4.5, books)
# 2. Map
transformed = map(
lambda b: {
"title": b["title"],
"category": b["genre"],
"is_long": b["pages"] > 300
},
high_rated
)
# 3. Cast to list
final_books = list(transformed)
print(final_books)Solution 5:
def chunk_generator(iterable, chunk_size):
chunk = []
for item in iterable:
chunk.append(item)
if len(chunk) == chunk_size:
yield chunk
chunk = []
if chunk: # Yield any remaining items
yield chunk
data = range(100) # 0 to 99
# Map applies the chunk_generator to the data.
# Note: map passes the whole iterable to the function once.
chunks_iter = map(lambda x: chunk_generator(x, 15), [data])
for chunk_list in chunks_iter:
for c in chunk_list:
print(c)(Alternative cleaner way for Ex 5 without map: just call chunk_generator(data, 15) directly, but this demonstrates how to pass an iterable through a map/lambda structure!)
Q1: B ([1, 9]) - Filter keeps 1, 3. Map squares them to 1, 9.
Q2: B - Assigning a lambda to a variable (square = ...) violates PEP 8. Use def.
Q3: False - In Python 3, map and filter return lazy iterators (generators).
Q4: D - Both A and C work. d.get("a", 0) is standard, and d["a"] or 0 works if you are sure "a" exists or you want 0 for falsy values (though get is safer). Correction for strictness: A is the safest and most Pythonic, but C technically evaluates to 0 if "a" is missing and throws KeyError, wait, if "a" is missing d["a"] throws KeyError. So A is the only strictly correct answer without try/except.
Correct Answer: A
Q5: B - Generator expressions evaluate lazily, yielding one item at a time, saving massive amounts of RAM.