পাঠ ০৬ · ২৫-এর মধ্যে · মডিউল ১

List comprehension ও iterator

Comprehensions & iterators — Pythonic transformations
৬ মিনিট পড়া শুরু · Beginner ব্রাউজারে কোড চালান

এই পাঠে যা শিখবেন

  • List comprehension — for loop-এর Pythonic পরিবর্ত
  • Filtering ও nested comprehension
  • Dict ও set comprehension
  • Generator expression — lazy iteration
  • Iterator protocol ও iter(), next()
  • কখন comprehension, কখন loop — ভারসাম্য

১ · List comprehension — কেন?

একই কাজ — তিন উপায়ে। কোনটা Pythonic?

Python
nums = [1, 2, 3, 4, 5]

# ❶ For loop — verbose
squared_v1 = []
for n in nums:
    squared_v1.append(n ** 2)

# ❷ map + lambda — functional, কিন্তু কম পঠনযোগ্য
squared_v2 = list(map(lambda x: x ** 2, nums))

# ❸ List comprehension — Pythonic ✓
squared_v3 = [n ** 2 for n in nums]

print(squared_v1)
print(squared_v2)
print(squared_v3)

    
Comprehension-এর গঠন

[expression for item in iterable if condition]

১) expression — প্রতিটি item কী হবে output-এ।
২) for ... in ... — কোন iterable-এ ঘুরবে।
৩) if condition — ঐচ্ছিক filter।

২ · Filter ও condition

Python
nums = list(range(20))

# জোড় সংখ্যার বর্গ
even_sq = [n ** 2 for n in nums if n % 2 == 0]
print(even_sq)

# Conditional expression — ternary
labels = ["pass" if n >= 50 else "fail" for n in [45, 67, 89, 32, 78]]
print(labels)

# Nested filter
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat_odd = [x for row in matrix for x in row if x % 2 == 1]
print(flat_odd)        # [1, 3, 5, 7, 9]

    
Nested loop-এ — বাইরের loop আগে। for row in matrix for x in row পড়ুন as: for row in matrix: for x in row:। ক্রম গুরুত্বপূর্ণ।

৩ · Dict ও set comprehension

Python
# Dict comprehension — word → length
words = ["AI", "Python", "ML", "data"]
lengths = {w: len(w) for w in words}
print(lengths)

# Vocabulary — id mapping
vocab = ["king", "queen", "man", "woman"]
word_to_id = {w: i for i, w in enumerate(vocab)}
id_to_word = {i: w for w, i in word_to_id.items()}
print(word_to_id)
print(id_to_word)

# Set comprehension — unique
text = "the quick brown fox jumps over the lazy dog"
unique_chars = {c for c in text if c.isalpha()}
print(sorted(unique_chars))

    
Vocabulary mapping — NLP-র অপরিহার্য কাজ। এই pattern token-id conversion-এর ভিত্তি — embeddings ও LLM-এর শুরু।

৪ · Generator — lazy iteration

List comprehension সব result একসাথে memory-তে তৈরি করে। ১০ কোটি item — RAM শেষ। GeneratorGeneratorএকটি iterator যা lazily মান তৈরি করে — চাইলে দেয়, না চাইলে নয়। yield keyword বা (expr for x in ...)। বিশাল ডেটা-তে অপরিহার্য। — চাইলে দেয়, না চাইলে নয়।

Python
# List vs Generator
list_comp = [n ** 2 for n in range(10)]      # সব এখনই তৈরি
gen_exp = (n ** 2 for n in range(10))        # () — generator

print(list_comp)         # [0, 1, 4, ...]
print(gen_exp)           # <generator object ...>

# Generator iterate
for sq in gen_exp:
    print(sq, end=" ")
print()

# একবারই — আবার লাগলে আবার তৈরি
print(list(gen_exp))     # [] — exhausted

# Memory পার্থক্য
import sys
big_list = [i for i in range(100_000)]
big_gen = (i for i in range(100_000))
print(f"List: {sys.getsizeof(big_list)} bytes")
print(f"Gen: {sys.getsizeof(big_gen)} bytes")

    
List ~৮০০ KB, generator ~২০০ bytes। বিশাল data-তে generator মেমোরি বাঁচায় হাজার গুণ। AI-তে data loader generator-এর উপরে।

৫ · yield — function-কে generator বানানো

Python
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a              # value দাও, পরে আবার আস
        a, b = b, a + b

# Generator function-এর ব্যবহার
for f in fibonacci(10):
    print(f, end=" ")        # 0 1 1 2 3 5 8 13 21 34
print()

# Batch generator — AI-তে আদর্শ
def batches(data, size):
    for i in range(0, len(data), size):
        yield data[i:i + size]

samples = list(range(1, 11))
for b in batches(samples, 3):
    print("Batch:", b)

    
List comp বনাম Generator eager vs lazy 📋 List Comprehension [x ** 2 for x in big] ⚡ সব value এখনই তৈরি RAM-এ সব একসাথে ✓ index access ✓ multiple iteration ✓ len() জানা ✗ বিশাল data → RAM full 🚀 Generator (x ** 2 for x in big) ⚡ চাইলে এক-একটা দেয় next() এক সময় একটি — lazy ✓ memory minimal ✓ infinite stream OK ✓ pipeline চেইন ✗ একবারই iterate AI data pipeline = চেইনে generator
List comp = সব এখন। Generator = "চাইলে দেব"। বিশাল ডেটা-তে generator-এর জুড়ি নেই।

৬ · Iterator protocol

Python
# for loop যা ভেতরে করে
fruits = ["আম", "জাম", "কাঁঠাল"]

it = iter(fruits)            # iterator তৈরি
print(next(it))              # আম
print(next(it))              # জাম
print(next(it))              # কাঁঠাল
# print(next(it))            # StopIteration

# zip — দু'টি iterable একসাথে
names = ["রহিম", "করিম", "ফাতেমা"]
ages = [20, 22, 19]
for n, a in zip(names, ages):
    print(f"{n}: {a}")

    
Comprehension সবসময় ভাল না। ৩-৫ লাইনের loop comprehend-এ পরিণত করলে — অপাঠ্য। জটিল হলে — explicit loop ভাল। Readability counts।

৭ · AI-তে comprehension/generator-এর প্রয়োগ

  • Feature engineering: [normalize(x) for x in data]
  • Token-id mapping: {w: i for i, w in enumerate(vocab)}
  • Batch loader: generator function — disk → tensor stream।
  • One-hot encoding: nested comprehension।
  • Pipeline: (transform(x) for x in source) — chain।
  • PyTorch DataLoader: internal-এ iterator protocol।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ List comprehension সবসময় explicit for loop-এর চেয়ে ভাল — সত্য নাকি মিথ? Performance ও readability — দু'টি দৃষ্টিকোণ থেকে কখন কোনটা?

এই বিতর্ক Python community-তে চিরন্তন। সঠিক উত্তর: "depends" — কিন্তু কখন কোনটা — এর সঠিক বোঝা ভাল kod ও অস্পষ্ট কোডের মধ্যে পার্থক্য।

Comprehension-এর performance:

  • ~২০-৩০% দ্রুত explicit loop-এর চেয়ে।
  • কারণ — bytecode-level optimization, append() call এড়ানো।
  • NumPy vectorize-এর তুলনায় তুচ্ছ — ১০০× ধীর।

Comprehension কখন বাছবেন:

  • Simple transform: [x*2 for x in lst] — clear।
  • Filter + transform: [f(x) for x in lst if cond(x)] — concise।
  • Dict/set creation: {k: v for k, v in pairs}
  • One-line, simple expression।

Loop কখন বাছবেন:

  • Side effect (print, log, db write) — comprehension এ বেমানান।
  • Multiple statements — comprehension একটি expression।
  • Try/except — loop ছাড়া উপায় নেই।
  • Multiple output — দুটি list একসাথে তৈরি — loop সহজ।
  • ৩+ nested loop — comprehension অপাঠ্য।

উদাহরণ — কী খারাপ comprehension:

# খারাপ
result = [
    [transform(x, y) for y in row if validate(y)]
    for row in matrix if any(check(y) for y in row)
]
# কয়েক sec বুঝতে যায় — loop-এ পরিষ্কার হত

"Side effect-এ comprehension" anti-pattern:

# খুব খারাপ
[print(x) for x in data]    # list তৈরির অপচয়, print-এর জন্য

# ভাল
for x in data:
    print(x)

NumPy/Pandas-এর সাথে priorities:

  • Numerical data → NumPy/Pandas vectorize, comprehension না।
  • Mixed type / Python objects → comprehension।
  • Dict construction → dict comprehension সবচেয়ে ভাল।

"Walrus" (Python 3.8+):

# যদি repeated computation এড়াতে চান
result = [y for x in data if (y := f(x)) > 0]
# y শুধু একবার compute

Readability heuristic:

  • One-line line ৭০-৮০ char-এর কম → comprehension OK।
  • Nested ২ level → debatable।
  • Nested ৩ level → ভুল।
  • আপনি ৬ মাস পরে নিজে পড়লে বুঝবেন? — yes → keep।

মূল উপলব্ধি: Comprehension = transform-এর syntactic sugar, না magic। সঠিক জায়গায় — elegant। অপব্যবহারে — illegible। "Readability counts" (Zen of Python)। সন্দেহ থাকলে — explicit loop।

প্র ০২ "Generator + yield" pattern — Python-এর memory-saving secret। Streaming data, infinite sequence, pipeline — কেন এটা AI/data engineering-এর backbone?

Generator — Python 2.2 (২০০১)-এ PEP 255-এ আসে। এটি functional programming-এর "lazy evaluation" idiom Python-এ আনে। আজকের AI data infra এর উপর দাঁড়িয়ে।

Lazy evaluation কী?

  • Eager: সব value এখনই compute, memory-তে।
  • Lazy: চাইলে এক-একটা দেয়, না চাইলে কিছু না।
  • Generator = Python-এর lazy stream।

Memory analysis:

# ১০ কোটি sample
import sys

list_data = [i for i in range(10**8)]    # ~৮ GB RAM!

gen_data = (i for i in range(10**8))     # ~২০০ bytes
# স্মৃতি save: ৪ কোটি গুণ

Pipeline composition — Unix pipe-এর মতো:

def read_lines(path):
    with open(path) as f:
        for line in f:
            yield line.strip()

def parse(lines):
    for line in lines:
        yield json.loads(line)

def filter_active(records):
    for r in records:
        if r["active"]:
            yield r

# Pipeline — কোন ধাপেই memory-তে full data নেই
pipeline = filter_active(parse(read_lines("huge.jsonl")))
for record in pipeline:
    process(record)

Infinite sequence:

def counter():
    i = 0
    while True:
        yield i
        i += 1

c = counter()
for n in c:
    if n > 5: break
    print(n)
# 0 1 2 3 4 5

AI/ML-এ critical applications:

  • PyTorch DataLoader: internal-এ iterator। Disk → batch তৈরি করে stream।
  • TensorFlow tf.data: generator-based pipeline।
  • HuggingFace datasets: streaming mode — terabyte ডেটা-ও memory-তে আনে না।
  • Image augmentation: on-the-fly transform — pre-compute না।
  • Tokenization: stream tokens through layers।
  • Sliding window: sequence model-এ context window।

Generator-এর advanced features:

  • send(): generator-এ value পাঠানো — coroutine-এর প্রাচীন রূপ।
  • throw(): exception inject।
  • yield from: sub-generator-এ delegate।
  • async generator (3.6+): await + yield।

itertools — generator-এর ভাণ্ডার:

  • chain, cycle, repeat — combine।
  • islice, takewhile, dropwhile — slice।
  • product, permutations, combinations — combinatorics।
  • groupby, tee — group ও duplicate।

সাবধানতা:

  • Single-pass — exhaustion-এর পর reset লাগে।
  • len() নেই — explicit count।
  • Index access নেই — only sequential।
  • Debug কঠিন — print স্থায়ী state বদলায়।

মূল উপলব্ধি: Generator = Python-এর scaling secret। ছোট লেনদেন list, বিশাল data generator। AI-তে — ১ TB log file process? Generator without thinking। DataLoader internals মাস্টার করতে — generator অপরিহার্য।

প্র ০৩ "Iterator protocol" Python-এর dunder magic-এর একটি। for loop ভেতরে কীভাবে কাজ করে? Custom iterator class কখন তৈরি করবেন?

Iterator protocol — Python-এর data model-এর foundation। এটা না বুঝলে — Python কীভাবে কাজ করে — অস্পষ্ট থাকবে।

Iterator protocol:

  • __iter__() — iterator object ফেরত।
  • __next__() — পরবর্তী value, না থাকলে StopIteration raise।

For loop ভেতরে যা করে:

# আপনি লিখলেন
for x in collection:
    use(x)

# Python ভেতরে
it = iter(collection)        # __iter__()
while True:
    try:
        x = next(it)         # __next__()
    except StopIteration:
        break
    use(x)

Iterable vs Iterator:

  • Iterable: __iter__ আছে — list, tuple, dict, str, file।
  • Iterator: __next__ আছে — iter()-এর result।
  • Iterator নিজেও iterable (__iter__ ফেরায় self)।

Custom iterator class:

class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

for n in CountDown(5):
    print(n)        # 5 4 3 2 1

সাধারণত generator function সহজ:

def countdown(start):
    while start > 0:
        yield start
        start -= 1

for n in countdown(5):
    print(n)

কখন class বাছবেন?

  • State management complex।
  • Multiple methods দরকার (reset, peek, etc.)।
  • Inheritance ও extensibility।
  • Type checking — isinstance(x, MyIterator)।

AI-তে এই pattern:

  • PyTorch Dataset + DataLoader:
    class MyDataset(Dataset):
        def __getitem__(self, idx): ...
        def __len__(self): ...
    
    # DataLoader auto-creates iterator wrapping this
  • Streaming HuggingFace dataset: internal iterator।
  • Random sampler: custom __iter__ with shuffling।
  • Curriculum learning: difficulty-ordered iterator।

Common built-in iterables:

  • range, enumerate, zip, map, filter
  • open() file — line-by-line iterator।
  • dict.keys(), .values(), .items()

iter() with sentinel:

# ফাইল chunked read
with open("big.bin", "rb") as f:
    for chunk in iter(lambda: f.read(4096), b""):
        process(chunk)
# b"" পেলে stop

মূল উপলব্ধি: Iterator protocol = Python-এর "duck typing" of iteration। যেকোনো object যা __iter__+__next__ দেয় — for loop-এ কাজ করে। PyTorch Dataset, NumPy ndarray, Pandas DataFrame — সব এই protocol-এ। বুঝলে — Python ecosystem-এর design গভীরে দেখা যায়।

প্র ০৪ NumPy array-এ arr * 2 কেন list comprehension-এর চেয়ে ১০০× দ্রুত? Vectorization ও comprehension-এর philosophical পার্থক্য কী? কখন কোনটা?

এই প্রশ্ন AI-র performance-এর হৃদয়ে নিয়ে যায়। List comprehension পrocessor-এর বন্ধু — কিন্তু GPU-র শত্রু।

List comprehension overhead:

  • প্রতি iteration — Python interpreter dispatch।
  • প্রতি object — type check, reference count।
  • প্রতি element — Python heap allocation।
  • Memory fragmentation — cache miss।

NumPy vectorization:

  • Contiguous C array — cache-friendly।
  • SIMD instruction — CPU একসাথে ৪/৮ float।
  • BLAS/LAPACK — হাজার মানুষ-বছরের optimization।
  • GIL release — multi-thread possible।

Concrete benchmark:

import numpy as np
import time

n = 10_000_000

# Comprehension
data = list(range(n))
t = time.time()
result = [x * 2 for x in data]
print(f"Comp: {time.time()-t:.3f}s")    # ~০.৫s

# NumPy
arr = np.arange(n)
t = time.time()
result = arr * 2
print(f"NumPy: {time.time()-t:.3f}s")   # ~০.০১s

Philosophical পার্থক্য:

  • Comprehension: "প্রতিটি element-এর জন্য কী?" — element-thinking।
  • Vectorization: "পুরো array-কে কী করব?" — array-thinking।
  • Mind shift গুরুত্বপূর্ণ।

Vectorization-এর সীমা:

  • Numerical-only — string operations চলে না (NumPy-তে limited)।
  • Branching — if per element কঠিন (np.where ব্যবহার)।
  • Sequential dependency — RNN-এর hidden state cumulative।
  • Mixed type collection — list comprehension।

Vectorization patterns:

# Conditional - np.where
arr_relu = np.where(arr > 0, arr, 0)

# Boolean mask
positives = arr[arr > 0]

# Reduce
mean = arr.mean()

# Outer product
xy = x[:, None] * y[None, :]    # broadcasting

কখন কোনটা:

  • Numerical, large data → NumPy/Pandas/PyTorch
  • Mixed types, small data, complex logic → comprehension
  • Side effects → for loop
  • Streaming, infinite → generator

Hybrid approach:

# Step 1: comprehension to clean
clean = [parse(x) for x in raw if valid(x)]

# Step 2: NumPy for math
arr = np.array(clean)
result = (arr - arr.mean()) / arr.std()

GPU-এ আরো দ্রুত:

  • PyTorch tensor.cuda() → ১০০০× speedup typical।
  • JAX jit compile → XLA optimization।
  • Numba @njit → Python loop → C speed।

মূল উপলব্ধি: Comprehension = Python idiom। Vectorization = math idiom। AI = math-heavy → vectorization default। কিন্তু comprehension-ও জানতে হবে — preprocessing, filtering, dict creation এ। দুটি tool — দুটি use case।

অনুশীলন

  1. One-liner: 1 থেকে 100-এর মধ্যে যেসব সংখ্যা ৩-এ বিভাজ্য কিন্তু ৫-এ নয় — list comprehension-এ বের করুন।
    result = [n for n in range(1, 101) if n % 3 == 0 and n % 5 != 0]
    print(result)
    # [3, 6, 9, 12, 18, 21, 24, 27, ...]
  2. Vocabulary: একটি বাক্যে — প্রতিটি unique word-কে index দিন (০ থেকে শুরু) — dict comprehension।
    text = "AI is fun and AI is the future"
    unique_words = sorted(set(text.lower().split()))
    vocab = {w: i for i, w in enumerate(unique_words)}
    print(vocab)
    # {'ai': 0, 'and': 1, 'fun': 2, 'future': 3, ...}
  3. Generator: infinite even number generator লিখুন। প্রথম ১০টি নিয়ে print করুন।
    def even_gen():
        n = 0
        while True:
            yield n
            n += 2
    
    import itertools
    first_10 = list(itertools.islice(even_gen(), 10))
    print(first_10)
    # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন।
পূর্ববর্তী পাঠ
পাঠ ০৫ · Function ও Lambda