Sharing notes from my ongoing learning journey — what I build, break and understand along the way.
Python Basics – Part 13: OOP, Lambdas, and Higher-Order Functions
OOP, Lambdas, and Higher-Order Functions in Python
In this part of the Python Basics series, we take a first look at object-oriented programming (OOP) in Python and then move on to anonymous functions (lambdas) and higher-order functions such as map, filter, and reduce.
The goal is not to turn you into a full-time OOP architect, but to give you a solid mental model of how classes, objects, and function-as-data patterns work in Python.
Object-Oriented Programming (OOP) in Python
OOP is a way of structuring programs around “things” (objects) that have data (attributes) and behavior (methods), rather than only around functions and raw data structures.
In Python, you define your own types using classes.
From a class, you create objects (instances) that carry their own state.
Defining a Simple Class
class Rectangle:
# Constructor (initializer)
def __init__(self, width, height):
# Instance attributes
self.width = width
self.height = height
rect1 = Rectangle(3, 4)
print(type(rect1)) # <class '__main__.Rectangle'>
print(rect1) # <__main__.Rectangle object at 0x...>
print(rect1.width, rect1.height) # 3 4
rect2 = Rectangle(23, 42)
print(rect2.width, rect2.height) # 23 42
Key points:
- class defines a new type.
- init is the constructor (more precisely: the initializer) that runs when you create an object.
- self refers to the current instance; you must write it explicitly in Python.
- Attributes like self.width and self.height belong to each individual object.
A class is like a blueprint; each object is a separate “real” rectangle with its own width and height.
Instance Methods
Methods are functions defined inside a class.
They describe what objects of that class can do.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rect1 = Rectangle(3, 4)
print(rect1.width, rect1.height) # 3 4
print(rect1.area()) # 12
rect2 = Rectangle(23, 42)
print(rect2.width, rect2.height) # 23 42
print(rect2.area()) # 966
Notes:
- The first parameter of an instance method is always self by convention.
- When you call rect1.area(), Python automatically passes rect1 as self.
- You can access and modify instance data inside methods using self.attribute_name.
Class Attributes (Static Attributes)
A class attribute is shared by all instances of the class.
It belongs to the class itself, not to a single object.
class Rectangle:
# Class attribute (static attribute)
counter = 0
def __init__(self, width, height):
self.width = width
self.height = height
# Increment class-level counter whenever a new object is created
Rectangle.counter = Rectangle.counter + 1
def area(self):
return self.width * self.height
rect1 = Rectangle(3, 4)
print(rect1.area()) # 12
print(Rectangle.counter) # 1
rect2 = Rectangle(23, 42)
print(rect2.area()) # 966
print(Rectangle.counter) # 2
Important details:
- Rectangle.counter is shared by all Rectangle objects.
- Accessing counter via the class name makes it clear it is a class-level attribute.
- You can also read it from an instance (rect1.counter), but writing to it that way would create an instance attribute that hides the class attribute. Using Rectangle.counter in the constructor is clearer.
Class attributes are often used for counters, configuration flags, or constants that apply to all instances.
Anonymous Functions (Lambda Functions)
Sometimes you need a very small function “on the fly” — often just to pass it as an argument into another function.
Instead of defining a full function with def, Python lets you create short anonymous functions using lambda.
Using def
def add(a, b):
return a + b
print(add(3, 4)) # 7
print(type(add)) # <class 'function'>
print(add) # <function add at 0x...>
Using lambda
add2 = lambda a, b: a + b
print(type(add2)) # <class 'function'>
print(add2(3, 4)) # 7
A lambda expression:
- has the form: lambda parameters: expression
- must contain a single expression (no statements inside)
- returns the value of that expression automatically
You can even call a lambda immediately without assigning it to a name:
print((lambda x, y: x + y)(3, 4)) # 7
Practical rule:
- Use def for anything non-trivial or reusable.
- Use lambda for very short, throwaway functions, especially when passing them to other functions.
Higher-Order Functions
A higher-order function is a function that takes other functions as arguments or returns a function.
Python has several built-in higher-order functions that work well with lambdas and normal functions.
map()
map() applies a function to each element of a sequence and returns a map object (an iterator).
# Using lambda
result = map(lambda x: x * x, range(2, 7))
print(type(result)) # <class 'map'>
print(list(result)) # [4, 9, 16, 25, 36]
You can also pass a normal function instead of a lambda:
def square(x):
return x * x
print(list(map(square, range(2, 7)))) # [4, 9, 16, 25, 36]
map() can take multiple sequences.
In that case, the function must accept one parameter per sequence:
print(list(map(lambda x, y: x * y, range(2, 7), range(12, 17))))
# [24, 39, 56, 75, 96]
Pythonic alternative: list comprehension
print([x * x for x in range(2, 7)]) # [4, 9, 16, 25, 36]
For many simple cases, comprehensions are more readable than map().
filter()
filter() keeps only the elements for which the given function returns True.
numbers = [2, 3, 7, 9]
filtered = filter(lambda x: x > 4, numbers)
print(list(filtered)) # [7, 9]
Here, the lambda returns True for x > 4, so only those elements remain.
Pythonic alternative using list comprehension:
print([x for x in numbers if x > 4]) # [7, 9]
reduce()
reduce() combines a sequence into a single value by repeatedly applying a function.
It lives in the functools module.
from functools import reduce
result = reduce(lambda x, y: x - y, [100, 33, 4, 42])
print(result) # 21
Step by step:
- Start with [100, 33, 4, 42]
- 100 – 33 = 67
- 67 – 4 = 63
- 63 – 42 = 21
In many cases, you don’t need reduce() in everyday Python code.
Often built-ins like sum(), min(), or a simple loop are clearer:
numbers = [100, 33, 4, 42]
total = sum(numbers)
print(total) # 179
reduce() is useful when your combination logic is more complex than “just sum”.
Summary
In this part you learned:
- How to define classes in Python using class and init.
- How instance attributes and methods work through self.
- How to use class attributes to store information shared by all instances.
- How to create small anonymous functions using lambda.
- How to use higher-order functions map(), filter(), and reduce().
- When to prefer Pythonic alternatives like list comprehensions and built-in functions.
