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

Function ও Lambda

Functions & lambdas — reusable code
৭ মিনিট পড়া শুরু · Beginner ব্রাউজারে কোড চালান

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

  • Function definition ও call — def, return
  • Parameter ও argument — positional, keyword, default
  • *args, **kwargs — variable arguments
  • Lambda — এক লাইনের anonymous function
  • Higher-order function: map, filter, sorted-এ key
  • Scope ও closure-এর প্রাথমিক ধারণা

১ · Function কেন?

একই কাজ বার বার লিখলে — bug ৫× জায়গায়, fix-ও ৫× জায়গায়। FunctionFunctionইনপুট নিয়ে আউটপুট দেওয়া পুনর্ব্যবহারযোগ্য কোডব্লক। Python-এ def দিয়ে define। AI-তে activation, loss, optimizer — সবই function। এক জায়গায় — call হাজার জায়গায়। DRY: Don't Repeat Yourself।

Function-এর ৪ অংশ

১) def keyword + নাম।
২) Parameter list — input।
৩) Body (indented) — কাজের ধাপ।
৪) return — output (ঐচ্ছিক, default None)।

Python
# Function define
def square(x):
    """একটি সংখ্যার বর্গ ফিরিয়ে দেয়।"""
    return x * x

def greet(name, greeting="শুভ সকাল"):
    """default argument সহ।"""
    return f"{greeting}, {name}!"

# Call
print(square(5))                # 25
print(square(2.5))              # 6.25
print(greet("রহিম"))             # default
print(greet("ফাতেমা", "নমস্কার"))

    
"""…""" = docstringDocstringFunction/class-এর ভেতরে প্রথম string — documentation। help(func)-এ দেখায়। AI library-তে standard। — function-এর ব্যাখ্যা। help(square) বা IDE hover-এ দেখাবে।

২ · Positional ও Keyword arguments

Python
def train(model, lr, epochs=10, verbose=True):
    return f"{model} trained with lr={lr}, {epochs} epochs"

# Positional — ক্রম গুরুত্বপূর্ণ
print(train("BERT", 0.001))

# Keyword — clarity, ক্রম free
print(train(model="GPT", lr=0.0001, epochs=20))

# Mixed — positional আগে
print(train("ResNet", 0.01, verbose=False))

    
AI library-তে hyperparameter অনেক — train(model, lr=..., epochs=..., batch_size=...)। Keyword-এ স্পষ্টতা — readability প্রথম priority।

৩ · *args ও **kwargs — flexible signature

Python
# *args — variable positional
def total(*nums):
    return sum(nums)

print(total(1, 2, 3))              # 6
print(total(1, 2, 3, 4, 5))        # 15

# **kwargs — variable keyword
def configure(**settings):
    for key, value in settings.items():
        print(f"  {key} = {value}")

configure(lr=0.001, batch=32, optimizer="adam")

# একসাথে
def model_fit(X, y, *, epochs=10, **extra):
    print(f"Training on {len(X)} samples")
    print(f"Epochs: {epochs}")
    print(f"Extra: {extra}")

model_fit([1,2,3], [0,1,0], epochs=5, lr=0.01, dropout=0.2)

    
*args tuple-এ collect, **kwargs dict-এ। * single asterisk এর পরে সব argument keyword-only। PyTorch/TensorFlow API-তে এই pattern সর্বত্র।

৪ · Lambda — তাৎক্ষণিক ছোট function

LambdaLambdaAnonymous function — নাম ছাড়াই, এক expression-এ। Alonzo Church-র lambda calculus (১৯৩০) থেকে নাম। Python-এ lambda args: expr। — যখন ছোট function তাৎক্ষণিক দরকার, পুরো def overkill।

Python
# def
def double(x):
    return x * 2

# একই — lambda
double_l = lambda x: x * 2

print(double(5), double_l(5))      # 10 10

# multi-arg lambda
add = lambda a, b: a + b
print(add(3, 4))                   # 7

# sorted-এ key — Pythonic
students = [("রহিম", 85), ("ফাতেমা", 92), ("করিম", 78)]
by_score = sorted(students, key=lambda s: s[1], reverse=True)
print(by_score)

    
Lambda শুধু single expression — if, for, multi-line — যাবে না। জটিল কাজ হলে — def ব্যবহার করুন। নাম দিন, docstring দিন।
Function — define ও call def → call → return Definition def square(x): """বর্গ ফেরত""" return x * x ↑ name ↑ parameter ↑ docstring ↑ body + return Call result = square(5) ↓ x = 5 (binding) ↓ return 5 * 5 = 25 ↓ result == 25 default arg def f(x, y=10): *args **kwargs def f(*a, **kw): lambda lambda x: x * 2
Function-এর শারীরবিদ্যা — define একবার, call হাজার বার। Variations চারটি — default, *args, **kwargs, lambda।

৫ · Higher-order function — function-কে data হিসেবে

Python-এ function "first-class citizen" — variable-এ রাখা যায়, list-এ ঢোকানো যায়, function-এ পাঠানো যায়। এটাই AI-র optimizer/loss callback-এর ভিত্তি।

Python
# Function variable-এ
def relu(x):
    return max(0, x)

activation = relu                # function পাস
print(activation(-3), activation(7))    # 0  7

# map — সব element-এ function apply
nums = [-2, -1, 0, 1, 2]
relu_out = list(map(relu, nums))
print(relu_out)                  # [0, 0, 0, 1, 2]

# filter — শর্ত মেলে এমন
positives = list(filter(lambda x: x > 0, nums))
print(positives)                 # [1, 2]

# function return করা
def make_multiplier(n):
    def inner(x):
        return x * n
    return inner

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5))      # 10  15

    
make_multiplier-এর ভেতরের inner outer-এর n "মনে রাখে" — এটাই closureClosureএকটি function যা enclosing scope-এর variable মনে রাখে। JavaScript ও Python উভয়ে আছে। Decorator-এর ভিত্তি। AI-তে stateful callback তৈরিতে।।

৬ · Scope — variable কোথায় বাঁচে?

Python
x = 10                    # global

def show():
    y = 5                 # local — শুধু এই function-এ
    print(f"local y = {y}, global x = {x}")

show()
print(x)
# print(y)              # NameError — local দেখা যাবে না

def modify():
    global x
    x = 100               # global x বদলে

modify()
print(x)                  # 100

    
global ব্যবহার এড়িয়ে চলুন — function-কে impure বানায়, test কঠিন। বদলে — argument পাঠান, return করুন।

৭ · AI-তে function-এর প্রয়োগ

  • Activation: ReLU, sigmoid, tanh — সব function।
  • Loss: MSE, cross-entropy — function যা y_true, y_pred নেয়।
  • Optimizer step: SGD, Adam — gradient → update function।
  • Data transform: normalize, augment — preprocessing function।
  • Model: PyTorch nn.Module-এর forward() = function।
  • Callback: on_epoch_end, on_batch_end — Keras/Lightning।

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

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

প্র ০১ "Pure function" বলতে কী বোঝায়? Functional programming-এর এই ধারণা AI/ML-এ কেন গুরুত্বপূর্ণ — testing, parallel, reproducibility-র দৃষ্টিতে?

Pure function — functional programming-এর soul। Side-effect-এর বিরুদ্ধে discipline।

Pure function-এর দু'টি শর্ত:

  • Same input → same output (deterministic)।
  • No side-effect — global state পরিবর্তন নেই, file/network/print নেই।

উদাহরণ:

# Pure
def add(a, b):
    return a + b

# Impure — side effect
counter = 0
def add_with_log(a, b):
    global counter
    counter += 1
    print(f"Call #{counter}")
    return a + b

Pure function-এর সুবিধা:

  • Testing সহজ: input দিলে output predictable। mock-এর দরকার নেই।
  • Parallelizable: shared state নেই → race condition নেই।
  • Memoizable: input → output cache করা যায়।
  • Composable: f(g(h(x))) safe।
  • Reasoning সহজ: "এই function এই input-এ কী করবে?" — predict possible।

AI/ML-এ এর তাৎপর্য:

  • Reproducibility: impure code → run-to-run different। ML paper-এ disaster।
  • Distributed training: Spark, Ray, JAX — pure function জরুরি।
  • JAX-এর JIT: pure function compile করে XLA-তে। Impure → silent bug।
  • Auto-differentiation: gradient হিসাব pure function ছাড়া meaningful নয়।

ML-এ অপরিহার্য impurity:

  • Logging — print, MLflow।
  • Random seed — সঠিকভাবে seed control করা।
  • GPU state — tensor moves, memory allocation।
  • File I/O — checkpoint save।

সমাধান — boundary-তে impurity:

  • "Functional core, imperative shell" — Gary Bernhardt।
  • Computation pure, I/O outside।
  • Unit test pure core। Integration test shell।

Random এর কী হবে?

# Impure — hidden state
def augment(image):
    return image + np.random.randn()    # ভিন্ন run, ভিন্ন result

# Pure — explicit randomness
def augment(image, rng):
    return image + rng.randn()

JAX এই philosophy-র champion — explicit PRNG key।

Side-effect-এর তালিকা:

  • Global variable মুটেট।
  • Argument mutate (list.append)।
  • File/network/db read/write।
  • print, logging।
  • Time, random — hidden input।
  • Exception raise (debatable)।

মূল উপলব্ধি: 100% pure code unrealistic। কিন্তু purity discipline = 90% bug elimination। AI engineer-এর জন্য — function-কে pure যথাসম্ভব রাখুন। Impurity boundary-তে contained। JAX-এর rise এই philosophy-র victory।

প্র ০২ Python-এ "mutable default argument" একটি কুখ্যাত bug source। def f(x=[]): কেন বিপজ্জনক? এই pitfall AI কোডে কীভাবে প্রকাশ পায়?

এই bug — Python-এর সবচেয়ে famous gotcha। প্রায় সব Python developer একবার করেছে। বুঝলে — চিরকাল বাঁচা।

সমস্যা:

def append_item(item, target=[]):
    target.append(item)
    return target

print(append_item("a"))    # ['a']
print(append_item("b"))    # ['a', 'b'] — কেন?!
print(append_item("c"))    # ['a', 'b', 'c']

কারণ:

  • Default argument function definition-এর সময় একবার evaluate হয়।
  • Function object-এর সাথে bind — পরের call-এ একই list।
  • Mutable হলে — append/modify shared।

সঠিক সমাধান — Sentinel pattern:

def append_item(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

AI কোডে এর প্রকাশ:

# খারাপ — একই config sharing
def train_model(name, history={}):
    history[name] = {"loss": []}
    return history

train_model("BERT")
train_model("GPT")    # BERT-এর history-ও সাথে!
  • Hyperparameter dict: default mutable → পুরনো run-এর leak।
  • Cache dict: function-call-এ cache shared (অনিচ্ছাকৃত)।
  • List of metrics: training run-এ data leak।
  • NumPy array default: একই reference, modification carry over।

কখন intentional?

  • Memoization (functools.lru_cache ভাল)।
  • Singleton state — global-এর alternative। Anti-pattern।

Type-checker সাহায্য:

  • mypy/pyright warn — mutable default detect।
  • ruff B006 rule — auto-detect।
  • CI-তে এই lint চালান।

Default best practices:

  • Mutable default → None + body-তে check।
  • Immutable (int, str, tuple, frozenset) → safe।
  • Type hint: list[int] | None = None

একটি advanced trick — frozen default:

from types import MappingProxyType

DEFAULT_CFG = MappingProxyType({"lr": 0.001})

def train(cfg=DEFAULT_CFG):
    # cfg.update(...)   # ← TypeError, immutable
    new_cfg = {**cfg, "epochs": 10}    # safe

মূল উপলব্ধি: Mutable default = time bomb। প্রথমবার কাজ করে, পরে কামড় দেয়। AI-তে — config, history, cache — খুব common surface। Sentinel pattern + linter = সমাধান।

প্র ০৩ Lambda বনাম regular function — কখন কোনটা? "Lambda-কে variable-এ assign করা" কেন anti-pattern? Real AI কোডে lambda কোথায় idiomatic?

Lambda Python-এ controversial। PEP 8 সাবধানে style guide দেয়। ভালো ব্যবহার — শক্তি; বেশি ব্যবহার — অস্পষ্টতা।

Lambda-র সীমা:

  • Single expression — if/for/try নেই (ternary OK)।
  • No statement — assignment, print নেই।
  • Default name <lambda> — stack trace-এ অস্পষ্ট।
  • No docstring।

Anti-pattern: lambda assign করা:

# খারাপ — PEP 8 violation
square = lambda x: x * x

# ভাল — def
def square(x):
    return x * x

কেন? def name দেয়, traceback ভাল, debugger-এ ভাল, docstring লেখা যায়।

Lambda কোথায় idiomatic:

  • sorted() key:
    students.sort(key=lambda s: s.score)
  • map/filter:
    list(map(lambda x: x ** 2, nums))
    (যদিও list comprehension প্রায় সবসময় ভাল)
  • Pandas apply:
    df["upper"] = df["name"].apply(lambda s: s.upper())
  • GUI callback:
    button.on_click(lambda: print("clicked"))
  • functools.reduce:
    reduce(lambda a, b: a * b, nums)

AI/ML-এ lambda-র জায়গা:

  • scikit-learn FunctionTransformer:
    from sklearn.preprocessing import FunctionTransformer
    log_tf = FunctionTransformer(lambda X: np.log1p(X))
  • PyTorch Lambda layer:
    nn.Sequential(
        nn.Linear(10, 20),
        nn.ReLU(),
        nn.Lambda(lambda x: x * 2)    # custom op
    )
  • DataFrame transformations: column-wise quick op।
  • Sorting model checkpoints: sorted(paths, key=lambda p: int(p.stem.split('-')[-1]))।

Lambda-র alternatives:

  • operator module:
    from operator import attrgetter, itemgetter
    sorted(students, key=attrgetter("score"))    # দ্রুত, পরিষ্কার
    sorted(items, key=itemgetter(1))
  • functools.partial:
    from functools import partial
    double = partial(multiply, 2)    # multiply(2, x)
  • List comprehension:
    [x ** 2 for x in nums]    # map+lambda-এর চেয়ে Pythonic

সিদ্ধান্তের সূত্র:

  • One-time, in-place, simple → lambda OK।
  • Reusable, named, complex → def।
  • Side-effect, multi-line → অবশ্যই def।
  • Existing equivalent (operator, partial) → use that।

মূল উপলব্ধি: Lambda = scalpel, not hammer। নির্দিষ্ট কাজে চমৎকার, সাধারণ কাজে def। PyTorch/Pandas-এ দরকারি tool, কিন্তু overuse code-কে obscure করে। "Will I read this in 6 months?" — উত্তর "না" হলে — def।

প্র ০৪ Decorator কী? @something দেখলে কী ঘটে? AI/ML library-তে — @torch.no_grad(), @jit.script, @app.route — কেন এত প্রচলিত?

Decorator — Python-এর সবচেয়ে elegant feature-এর একটি। PEP 318 (২০০৩)-এ আসে। AI library design-এ কেন্দ্রীয়।

মূল ধারণা:

  • Function-কে input হিসেবে নেয়, modified function ফেরত দেয়।
  • @decorator = syntactic sugar for func = decorator(func)।
  • "Behavior wrap"— original function-এ before/after logic।

মৌলিক উদাহরণ:

def timer(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time()-start:.4f}s")
        return result
    return wrapper

@timer
def slow_op():
    sum(i**2 for i in range(10**6))

slow_op()    # auto-print timing

AI/ML-এ decorator-এর প্রভাব:

  • @torch.no_grad(): Inference-এ gradient disable। Memory ৫০% সাশ্রয়।
    @torch.no_grad()
    def predict(model, x):
        return model(x)
  • @jit.script (TorchScript): Python function → optimized graph।
  • @tf.function: TensorFlow eager → graph mode।
  • @jax.jit: XLA compilation, ১০-১০০× speedup।
  • @cache / @lru_cache: Memoization — repeated computation এড়াতে।
  • @dataclass: Auto-generate __init__, __repr__।
  • @property: Method → attribute-like access।
  • @app.route("/predict"): Flask/FastAPI — endpoint registration।

Decorator with arguments:

def retry(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if i == times - 1: raise
        return wrapper
    return decorator

@retry(times=3)
def fetch_data():
    ...

Class decorator vs function decorator:

  • Class — state রাখা যায়। যেমন: counter, cache।
  • Function — stateless wrap।
  • functools.wraps — original metadata preserve।

Decorator stacking:

@cache
@validate_input
@log_calls
def expensive_op(x):
    ...
# expensive_op = cache(validate_input(log_calls(expensive_op)))
  • নিচ থেকে উপরের দিকে apply — order matters।

সমস্যা ও সাবধানতা:

  • functools.wraps ছাড়া — original name, docstring lost।
  • Stack trace জটিল হয়।
  • Type hint preservation কঠিন (Python 3.12-এ ParamSpec ভাল)।
  • Debugger step-into through wrapper।

Built-in important decorators:

  • @staticmethod, @classmethod — class methods।
  • @property — getter।
  • @functools.cache (3.9+) — simple memoization।
  • @functools.wraps — meta preserve।
  • @contextlib.contextmanager — with-statement।

মূল উপলব্ধি: Decorator = behavior modification declarative। AI library-তে — boilerplate কমানোর primary tool। @torch.no_grad() না বুঝলে — PyTorch inference loop confusing। Decorator master হলে — Python-এর সৌন্দর্য full বুঝবেন।

অনুশীলন

  1. Activation function: একটি function লিখুন relu(x) যা negative-কে 0 ও positive-কে অপরিবর্তিত রাখে। List-এর প্রতিটি element-এ apply করুন।
    def relu(x):
        return max(0, x)
    
    nums = [-3, -1, 0, 2, 5]
    print([relu(n) for n in nums])
    # অথবা
    print(list(map(relu, nums)))
    # [0, 0, 0, 2, 5]
  2. Multi-return: একটি function লিখুন stats(nums) যা min, max ও average tuple-এ ফেরায়।
    def stats(nums):
        return min(nums), max(nums), sum(nums) / len(nums)
    
    mn, mx, avg = stats([10, 20, 30, 40, 50])
    print(f"min={mn}, max={mx}, avg={avg}")
    # min=10, max=50, avg=30.0
  3. Lambda sort: students = [("রহিম", 85), ("ফাতেমা", 92), ("করিম", 78)] — score ascending ও descending — দু'ভাবে sort করুন lambda দিয়ে।
    students = [("রহিম", 85), ("ফাতেমা", 92), ("করিম", 78)]
    
    asc = sorted(students, key=lambda s: s[1])
    desc = sorted(students, key=lambda s: s[1], reverse=True)
    
    print("Ascending:", asc)
    print("Descending:", desc)

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন।
পূর্ববর্তী পাঠ
পাঠ ০৪ · if/else ও loop