Constructor, Iterator, Generator, and Decorator

Introduction

In this chapter, you will learn four powerful Python concepts: constructor, iterator, generator, and decorator. These tools help you write cleaner object-oriented code, process data efficiently, and extend function behavior without changing core logic. Once you understand them, your Python skill set becomes much more advanced and practical.

Prerequisites

  • Python 3.10+ installed
  • Basic understanding of classes, functions, loops, and exceptions
  • Ability to run .py files in terminal or IDE

1) Constructor (__init__)

A constructor is a special method used to initialize object attributes when creating a class instance.

python
# Define class with constructor
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
 
    def show(self):
        print(f"{self.name}: {self.score}")
 
 
# Create object
stu = Student("Emma", 95)
stu.show()

Why constructor matters:

  • ensures object has required initial state
  • reduces repetitive setup code

2) Iterator

An iterator is an object that returns one item at a time, usually via __iter__() and __next__().

Python built-ins like lists are iterable, and you can create custom iterators too.

python
# Custom iterator class
class Counter:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0
 
    def __iter__(self):
        return self
 
    def __next__(self):
        if self.current < self.limit:
            self.current += 1
            return self.current
        raise StopIteration
 
 
# Use custom iterator
for num in Counter(5):
    print(num)

Iterator gives controlled step-by-step data access.

3) Generator

A generator is a simpler way to create iterators using yield.

python
# Generator function
def count_up_to(limit):
    num = 1
    while num <= limit:
        yield num
        num += 1
 
 
# Use generator
for num in count_up_to(5):
    print(num)

Difference from normal function:

  • return ends function once
  • yield pauses and resumes function state

Tip

Why Generators Are Great

Generators are memory-friendly for large data streams because they produce values lazily.

4) Decorator

A decorator wraps a function to add extra behavior before/after execution.

python
# Define decorator
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function: {func.__name__}")
        result = func(*args, **kwargs)
        print("Function finished.")
        return result
    return wrapper
 
 
# Apply decorator
@log_call
def say_hello(name):
    print(f"Hello, {name}!")
 
 
# Call function
say_hello("Liam")

Decorator use cases:

  • logging
  • timing
  • permission checks
  • retry wrappers

5) Real Mini Example: Student Score Pipeline

This example combines all four concepts in a lightweight way.

python
# Decorator: log function calls
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] Start: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[LOG] End: {func.__name__}")
        return result
    return wrapper
 
 
# Class with constructor
class Student:
    def __init__(self, name, scores):
        self.name = name
        self.scores = scores
 
 
# Generator: produce score values one by one
def score_generator(scores):
    for score in scores:
        yield score
 
 
# Iterator class: iterate over student names
class StudentNameIterator:
    def __init__(self, students):
        self.students = students
        self.index = 0
 
    def __iter__(self):
        return self
 
    def __next__(self):
        if self.index < len(self.students):
            name = self.students[self.index].name
            self.index += 1
            return name
        raise StopIteration
 
 
# Decorated function
@log_call
def print_student_report(student):
    total = sum(score_generator(student.scores))
    avg = total / len(student.scores)
    print(f"{student.name} average score: {avg:.2f}")
 
 
# Demo data
students = [
    Student("Emma", [95, 88, 92]),
    Student("Noah", [78, 85, 80]),
]
 
# Use iterator for names
print("Student names:")
for name in StudentNameIterator(students):
    print(name)
 
# Use decorated report function
for student in students:
    print_student_report(student)

This shows how these concepts can work together in real code structure.

Warning

Do not use advanced patterns just for style.
Use them when they solve real readability, reuse, or performance problems.

Common Beginner Mistakes

Mistake 1: Confusing Iterable and Iterator

Iterable can be looped over; iterator is the stateful object that produces next values.

Mistake 2: Using return Instead of yield in Generator

return ends function immediately, so it will not behave like a generator stream.

Mistake 3: Forgetting *args and **kwargs in Decorator Wrapper

Without them, decorated functions with parameters can break.

Surprise Practice Challenge

Build a "Reading Task Tracker" project:

  1. Class Task with constructor (title, done)
  2. Generator that yields unfinished task titles
  3. Custom iterator for all task objects
  4. Decorator that logs each task-update function call
  5. Print final summary report

If you finish this, you can confidently use intermediate Python design patterns.

FAQ

Should beginners use custom iterators often?

Not often in simple scripts. Start with generators first, then custom iterators when needed.

Are generators always better than lists?

Not always. Generators are better for streaming/lazy processing; lists are better when you need random access or repeated traversal.

Do decorators change the original function code?

They do not require editing internal logic, but they wrap function behavior externally.

When should I avoid decorators?

Avoid decorators when the team is unfamiliar with them and the added abstraction hurts readability.