সরাসরি পর্বে যান
পর্ব ৪৪ · ৫৫-এর মধ্যে · মডিউল ৭

Pythonic কোড — PEP 8, idiom আর ruff

Pythonic Code: The Zen of Python, PEP 8, Everyday Idioms, Refactoring and Automatic Checks with Ruff

Taught in Bangla, this lesson teaches readable, idiomatic Python: the core of PEP 8 for names, lines and imports, everyday idioms shown before and after, a step-by-step refactor, names, docstrings and comments, tooling with ruff, black, isort, mypy and pre-commit, common anti-patterns and code review, and spotting non-Pythonic AI-written code.

এই পর্বে যা শিখবেন

  • “Pythonic” কথাটির আসল মানে, আর Zen of Python-এর পাঁচটি নীতি কোডে কেমন দেখায়
  • PEP 8-এর মূল কথা — নাম, লাইনের দৈর্ঘ্য, import-এর তিন দল, ফাঁকা জায়গা — আর কখন নিয়ম ভাঙবেন
  • প্রায় বিশটি প্রতিদিনের idiom আগে-পরে জোড়ায়, আর একটি অগোছালো ফাংশনকে ধাপে ধাপে Pythonic বানানো
  • ভালো নাম, docstring, “কেন”-ব্যাখ্যা করা comment আর public API-তে type hint
  • ruff, black, isort, mypy, pre-commit দিয়ে স্বয়ংক্রিয় যাচাই; code review-র আদব আর সাধারণ anti-pattern; AI-এর কোড পরিষ্কার করা

১ · কোড পড়া হয় বেশি, লেখা হয় কম — “Pythonic” মানে কী

রাজশাহীর একটি কোচিং সেন্টারের ফলাফল তৈরির স্ক্রিপ্ট লিখেছিলেন তানভীর, দুই বছর আগে। স্ক্রিপ্টটি ঠিকঠাক চলে। এখন তানভীর চাকরি নিয়ে ঢাকায়, আর স্ক্রিপ্টে একটি নতুন নিয়ম যোগ করার দায়িত্ব পেয়েছেন নুসরাত। তিনি ফাইল খুলে দেখলেন: ভ্যারিয়েবলের নাম a, tmp2, lst; একটি ফাংশন দুশো লাইনের; পাঁচ স্তর গভীর if; কোথাও কোথাও 40 আর 33 সংখ্যা, কোনো ব্যাখ্যা ছাড়া। একটি লাইন বদলাতে নুসরাতের লাগল দুই দিন — এক দিন শুধু বুঝতেই।

স্ক্রিপ্টটি “ভুল” ছিল না। কম্পিউটার ঠিকই বুঝেছিল। সমস্যা ছিল মানুষের বোঝায়। এখানেই এই পর্বের মূল কথা: কোড একবার লেখা হয়, কিন্তু পড়া হয় বহুবার — বাগ খুঁজতে, নতুন ফিচার যোগ করতে, review করতে, শিখতে। PEP 8-এর শুরুর দিকেই লেখা আছে Guido van Rossum-এর এই উপলব্ধি: কোড যতবার লেখা হয়, তার চেয়ে অনেক বেশিবার পড়া হয়। তাই লেখার সময় দুই মিনিট বেশি খরচ করে যদি পড়ার সময় দুই ঘণ্টা বাঁচে, সেটাই লাভ।

ঢাকার একটি বাসের রুট-নম্বরের কথা ভাবুন। যদি প্রতিটি কোম্পানি নিজের মতো সংকেত দিত — কেউ রঙ দিয়ে, কেউ প্রাণীর ছবি দিয়ে, কেউ উল্টো দিক থেকে নম্বর — তাহলে প্রতিটি বাসে ওঠার আগে আপনাকে নতুন করে শিখতে হতো। একই নিয়ম সবাই মানলে আপনি এক নজরে বোঝেন। Python-এর প্রচলিত ধাঁচও তাই: for i, name in enumerate(names) দেখলে যেকোনো Python প্রোগ্রামার — বাংলাদেশে হোক বা ব্রাজিলে — সঙ্গে সঙ্গে বোঝেন কী হচ্ছে। নিজের বানানো কৌশল দেখলে তাঁকে থেমে ভাবতে হয়।

এই প্রচলিত ধাঁচের নাম idiom — ভাষার এমন প্রকাশভঙ্গি, যা ওই ভাষার লোকেরা স্বাভাবিকভাবে ব্যবহার করেন। বাংলায় “মাথা খাওয়া” বললে সবাই বোঝেন, যদিও শব্দ ধরে অনুবাদ করলে অর্থহীন। আর যে কোড Python-এর idiom ব্যবহার করে লেখা, তাকে বলে Pythonic. উল্টোটা হলো অন্য ভাষার অভ্যাস Python-এ টেনে আনা: C-এর মতো সূচক ধরে লুপ, Java-র মতো প্রতিটি attribute-এর getter. এগুলো চলে, কিন্তু Python প্রোগ্রামারের চোখে “বিদেশি উচ্চারণ”-এর মতো লাগে — বুঝতে একটু বেশি চেষ্টা লাগে, আর ভুল লুকানোর জায়গা বাড়ে।

স্পষ্ট বনাম চালাক

Pythonic মানে সবচেয়ে ছোট বা সবচেয়ে চালাক কোড নয়। এক লাইনে পাঁচটি কাজ ঠেসে দেওয়া Pythonic নয়, যদি পড়তে গিয়ে থামতে হয়। মাপকাঠি একটাই: একজন সাধারণ Python প্রোগ্রামার কত দ্রুত আর কত নিশ্চিতভাবে বুঝবেন কোডটি কী করে. আর এর বাড়তি লাভ: যে কোড পরিষ্কার, তাতে বাগ চোখে পড়ে সহজে।

Zen of Python — পাঁচটি নীতি, কোডে

Python-এর দর্শন সবচেয়ে সংক্ষেপে লেখা আছে Zen of Python-এ — Tim Peters-এর লেখা ১৯টি ছোট বাক্য, পরে PEP 20 হিসেবে সংরক্ষিত। মজার ব্যাপার, এটি Python-এর ভেতরেই লুকানো: import this লিখলেই ছাপা হয়। নিচের প্রোগ্রামটি পুরো Zen পড়ে, তার থেকে পাঁচটি নীতি বেছে দেখায় — আর প্রোগ্রামটি নিজেও Pythonic ধাঁচে লেখা:

import contextlib
import io

buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
    import this  # noqa: F401 — import করলেই পুরো Zen ছাপা হয়, এখানে ধরে রাখছি

lines = buffer.getvalue().splitlines()
aphorisms = lines[2:]  # প্রথম লাইন শিরোনাম, দ্বিতীয়টি ফাঁকা
print(lines[0], f"— মোট {len(aphorisms)}টি নীতি")
for number in (1, 3, 7, 13, 17):
    print(f"{number:>2}. {aphorisms[number - 1]}")

আউটপুট ছয় লাইন: The Zen of Python, by Tim Peters — মোট 19টি নীতি, তারপর 1. Beautiful is better than ugly., 3. Simple is better than complex., 7. Readability counts., 13. There should be one-- and preferably only one --obvious way to do it. আর 17. If the implementation is hard to explain, it's a bad idea. — import this নিজে থেকেই ছাপে, তাই পর্ব ৩৬-এর context manager redirect_stdout দিয়ে আমরা সেই লেখা একটি StringIO-তে ধরে রেখেছি। # noqa: F401 হলো linter-কে বলা “এই import ব্যবহার না হওয়াটা ইচ্ছাকৃত” (অংশ ৭-এ দেখবেন)। লক্ষ করুন Pythonic ছোঁয়াগুলো: অর্থবহ নাম (aphorisms, number), slicing দিয়ে শিরোনাম বাদ, f-string-এ :>2 দিয়ে সংখ্যা ডানে সাজানো।

এই পাঁচটি নীতি কোডে কেমন দেখায়:

  • Beautiful is better than ugly: total=price*qty+vat নয়, total = price * qty + vat — একই কাজ, চোখের আরাম আলাদা।
  • Simple is better than complex: নিজে dict-এ গণনা না করে collections.Counter(words); নিজের sort না লিখে sorted().
  • Readability counts: d নয়, days_since_payment. নাম লম্বা হলেও পাঠক থামেন না।
  • There should be one obvious way to do it: list-এর প্রতিটি উপাদানে সূচকসহ লুপ? enumerate. দুটি list পাশাপাশি? zip. প্রচলিত একটি পথ থাকলে সবাই সেটি চেনে।
  • If the implementation is hard to explain, it's a bad idea: কোড রিভিউতে যদি এক লাইন বোঝাতে পাঁচ মিনিট লাগে, লাইনটি নতুন করে লিখুন।

Zen-এর বাক্যগুলো Tim Peters ১৯৯৯ সালে Python-এর মেইলিং লিস্টে লিখেছিলেন — Guido-র নকশার দর্শন ২০টি বাক্যে ধরার চেষ্টা হিসেবে। তিনি ১৯টি লিখে শেষটি “Guido-র জন্য” ফাঁকা রেখেছিলেন; সেটি আজও ফাঁকা। ২০০৪ সালে এগুলো PEP 20 হিসেবে আনুষ্ঠানিক রূপ পায়। আর this module-এর নিজের source code ইচ্ছা করে দুর্বোধ্য করে লেখা (লেখাটি ROT13 সংকেতে লুকানো) — Zen-এর ঠিক উল্টো, একটি সূক্ষ্ম রসিকতা।

২ · PEP 8-এর মূল কথা — নাম, লাইন আর ফাঁকা জায়গা

PEP 8 হলো Python কোডের অফিসিয়াল style guide — ২০০১ সালে লেখা, এখনো হালনাগাদ হয়। এটি আইন নয়; Python interpreter PEP 8 না মানলেও কোড চালায়। কিন্তু প্রায় সব Python লাইব্রেরি, কোম্পানি আর open-source প্রকল্প এটিকে ভিত্তি ধরে। তাই PEP 8 জানা মানে Python জগতের “প্রমিত বানান” জানা। পুরোটা মুখস্থের দরকার নেই — টুল বেশিরভাগ মনে রাখে (অংশ ৭)। তবে মূল কথাগুলো জানা চাই, কারণ টুল কী বলছে আর কেন, সেটা বুঝতে হবে।

নামের নিয়ম — নাম দেখেই জাত চেনা

কীধাঁচউদাহরণ
ভ্যারিয়েবল, ফাংশন, method, modulesnake_case — ছোট হাতের অক্ষর, শব্দের মাঝে _total_marks, send_sms(), result_report.py
class, exceptionPascalCase (PEP 8-এর ভাষায় CapWords)Student, BkashPayment, InvalidMarkError
ধ্রুবক (module-স্তরে)UPPER_SNAKE_CASEPASS_MARK = 40, MAX_RETRIES = 3
“ভেতরের ব্যবহারের” নামসামনে একটি _ (পর্ব ২২-এর private name)_parse_row(), self._balance
keyword-এর সাথে সংঘর্ষশেষে একটি _class_, type_
যে নাম এড়াবেনএকক l, O, I — অনেক ফন্টে 1 আর 0-এর মতো দেখায়l = 1 → length = 1

নামের ধাঁচ শুধু সৌন্দর্য নয় — এটি তথ্য বহন করে। Student(...) দেখলেই বোঝেন একটি class থেকে object বানানো হচ্ছে; student(...) হলে এটি একটি ফাংশন। PASS_MARK দেখলে বোঝেন এটি বদলানোর জিনিস নয়। আর _parse_row বলে দেয়: “এটি module-এর বাইরের কারও জন্য নয়, যেকোনো সময় বদলাতে পারে।”

লাইনের দৈর্ঘ্য — ৭৯, ৮৮ না ৯৯?

PEP 8 বলে: কোডের লাইন সর্বোচ্চ ৭৯ অক্ষর, আর docstring ও comment ৭২ অক্ষর। কারণগুলো বাস্তব — পাশাপাশি দুটি ফাইল খোলা, diff দেখার টুল, ছোট ল্যাপটপ বা মোবাইলে review. তবে PEP 8 নিজেই ছাড় দেয়: দল একমত হলে কোডের সীমা ৯৯ পর্যন্ত বাড়ানো যায়। আর black (অংশ ৭) ঠিক করেছে ৮৮ — তাদের যুক্তি, ৮০-র চেয়ে একটু বেশি জায়গায় অনেক অপ্রয়োজনীয় লাইন-ভাঙা কমে, অথচ ১০০-র মতো লম্বাও হয় না। আজ অনেক প্রকল্প ৮৮ ব্যবহার করে। কোনটি “ঠিক”, তা নিয়ে তর্কের চেয়ে জরুরি হলো: প্রকল্পে একটি সংখ্যা ঠিক করে টুলে লিখে দিন, তারপর আর ভাববেন না।

লম্বা লাইন ভাঙার Pythonic উপায় হলো বন্ধনী — (), [], {}-এর ভেতরে Python নিজেই লাইন জোড়া ধরে নেয়। পুরোনো ধাঁচের \ দিয়ে লাইন ভাঙা এড়িয়ে চলুন; তার পরে ভুল করে একটি space থাকলেই SyntaxError.

# এড়িয়ে চলুন
total = first_term_marks + second_term_marks + \
    practical_marks + attendance_bonus

# Pythonic — বন্ধনীর ভেতরে, অপারেটর লাইনের শুরুতে
total = (
    first_term_marks
    + second_term_marks
    + practical_marks
    + attendance_bonus
)

ফাঁকা জায়গা আর ফাঁকা লাইন

  • অপারেটরের দুই পাশে একটি space: x = a + b, if count >= 3:. তবে keyword argument বা default-এ =-এর পাশে space নয়: print(msg, end=""), def greet(name="অতিথি"): — কিন্তু type hint থাকলে আবার space: def greet(name: str = "অতিথি"):.
  • কমার পরে space, বন্ধনীর ভেতরে নয়: f(a, b), [1, 2, 3] — f( a,b ) নয়।
  • Indentation চার space, tab নয় (পর্ব ০৪)।
  • ফাঁকা লাইন: top-level ফাংশন আর class-এর আগে-পরে দুটি; class-এর ভেতরে method-এর মাঝে একটি; ফাংশনের ভেতরে যুক্তির “অনুচ্ছেদ” আলাদা করতে মাঝেমধ্যে একটি।

“A Foolish Consistency…” — কখন নিয়ম ভাঙবেন

PEP 8-এর একটি অংশের শিরোনাম মার্কিন লেখক Ralph Waldo Emerson-এর বিখ্যাত বাক্য থেকে নেওয়া: A Foolish Consistency is the Hobgoblin of Little Minds — নির্বোধ ধারাবাহিকতা ছোট মনের ভূত। এর বক্তব্য: style guide-এর উদ্দেশ্য পাঠযোগ্যতা; যেখানে নিয়ম মানলে কোড কম পাঠযোগ্য হয়, সেখানে নিয়ম ভাঙুন। PEP 8 কয়েকটি বৈধ কারণ বলে: নিয়ম মানলে পড়া কঠিন হয়; আশেপাশের পুরোনো কোড অন্য ধাঁচে লেখা আর সেখানে মিল রাখা জরুরি; কোডটি নিয়মের আগের; বা পুরোনো Python version-এর সাথে মিল রাখতে হয়। মনে রাখার সূত্র: একটি প্রকল্পের ভেতরে ধারাবাহিকতা PEP 8-এর চেয়েও জরুরি, আর একটি ফাংশনের ভেতরে ধারাবাহিকতা সবচেয়ে জরুরি.

উদাহরণ: গণিতের সূত্র কোডে লিখছেন — y = a*x**2 + b*x + c. এখানে গুণের পাশে space না দিয়ে যোগের পাশে দেওয়া আসলে অগ্রাধিকার চোখে দেখায়; PEP 8 নিজেই এমন উদাহরণ দেয়। বা একটি লাইব্রেরি camelCase ব্যবহার করে (যেমন পুরোনো unittest-এর setUp) — তার subclass-এ আপনিও সেই নাম রাখবেন, PEP 8 না মেনে।

৩ · import সাজানো — তিন দল, একটি ক্রম

একটি ফাইলের শুরুর import-গুলো তার “উপকরণের তালিকা” — কী কী লাগছে, কোথা থেকে আসছে। এলোমেলো তালিকা পড়ে বোঝা কঠিন: কোনটি Python-এর সাথেই আসে, কোনটি pip দিয়ে বসাতে হবে, কোনটি আমাদেরই প্রকল্পের? PEP 8-এর নিয়ম: প্রতিটি import আলাদা লাইনে, ফাইলের একদম শুরুতে (module docstring আর comment-এর পরে), আর তিনটি দলে — মাঝে একটি করে ফাঁকা লাইন। ধাপে ধাপে দেখুন:

ধাপ ১এলোমেলো — কোনটি কোথা থেকে?
from shop.models import Product
import requests
import os, sys
from pathlib import Path
import pandas as pd
from .utils import money
import json
from collections import defaultdict
from math import *
ধাপ ২দল ১ — standard library
import json
import os
import sys
from collections import defaultdict
from pathlib import Path
# ... বাকিগুলো এখনো সাজানো হয়নি
ধাপ ৩দল ২ — third-party (pip দিয়ে বসানো)
import json
import os
import sys
from collections import defaultdict
from pathlib import Path

import pandas as pd
import requests
ধাপ ৪দল ৩ — নিজের প্রকল্প (local)
import json ... from pathlib import Path

import pandas as pd
import requests

from shop.models import Product
from shop.utils import money
ধাপ ৫চূড়ান্ত — টুল নিজেই এভাবে সাজায়
import json
import os
import sys
from collections import defaultdict
from pathlib import Path

import pandas as pd
import requests

from shop.models import Product
from shop.utils import money
  1. এলোমেলো তালিকা। তিন জায়গার import মেশানো। দুটি স্পষ্ট ভুলও আছে: import os, sys এক লাইনে দুটি module (PEP 8 প্রতিটি আলাদা লাইনে চায়), আর from math import * — পর্ব ২২-এর wildcard import, যা কোন নাম কোথা থেকে এল তা লুকিয়ে ফেলে। এখানে math-এর কিছুই আসলে লাগছিল না, তাই এটি বাদ।
  2. দল ১: standard library. Python-এর সাথেই যা আসে — json, os, pathlib, collections. দলের ভেতরে বর্ণানুক্রমে; প্রচলিত টুলগুলো (isort, ruff) আগে সব import x, তারপর from x import y রাখে। এক নজরেই বোঝা যায়, এগুলোর জন্য কিছু ইনস্টল করতে হবে না।
  3. দল ২: third-party package. যা pip install দিয়ে আনতে হয় (পর্ব ২৩) — pandas, requests. নতুন কেউ প্রকল্প চালাতে গিয়ে error পেলে প্রথমেই এই দলটি দেখেন: “এগুলো কি আমার environment-এ আছে?”
  4. দল ৩: নিজের প্রকল্প। আপনার নিজের package-এর module. PEP 8 absolute import (from shop.utils import money) পছন্দ করে, কারণ পড়লেই বোঝা যায় ফাইলটি কোথায়; প্যাকেজের ভেতরে relative import (from .utils import money) গ্রহণযোগ্য, আর ruff-এর মতো টুল সেটিকে এই দলের শেষে আলাদা করে রাখে। এখানে আমরা .utils-কে absolute রূপে লিখেছি।
  5. চূড়ান্ত রূপ। তিন দল, মাঝে একটি করে ফাঁকা লাইন, দলের ভেতরে বর্ণানুক্রম। ভালো খবর: এটি হাতে করার দরকার নেই। ruff check --select I --fix বা isort এক সেকেন্ডে ঠিক এভাবে সাজিয়ে দেয় — অংশ ৭-এ দেখবেন। আপনার কাজ শুধু নিয়মটা বোঝা, যাতে টুলের বদল দেখে অবাক না হন।

৪ · আগে-পরে: প্রতিদিনের Python idiom

এই কোর্সের আগের পর্বগুলোতে প্রতিটি idiom আলাদা আলাদা শিখেছেন। এখানে সেগুলো একসাথে, আগে-পরে জোড়ায় — যাতে অন্যের কোড (বা AI-এর কোড) পড়ার সময় “আগে”-র ধাঁচ দেখলেই চিনতে পারেন। প্রতিটি “আগে” কোড ঠিকঠাক চলে; সমস্যা পাঠযোগ্যতা আর লুকানো ভুলের ঝুঁকি। বিষয় অনুযায়ী ট্যাব বেছে নিন:

# আগে                                   # পরে
if len(items) > 0:                      if items:
if done == True:                        if done:
if result == None:                      if result is None:
if city == "ঢাকা" or city == "চট্টগ্রাম":  if city in {"ঢাকা", "চট্টগ্রাম"}:
if mark >= 40 and mark <= 100:          if 40 <= mark <= 100:
found = False                           found = any(m < 33 for m in marks)
for m in marks:
    if m < 33:
        found = True

Truthiness (পর্ব ১১): খালি list, string, dict, 0 আর None নিজেই “মিথ্যা”, তাই if items: যথেষ্ট। তবে সাবধান: যদি 0 একটি বৈধ মান হয় (যেমন নম্বর ০), তখন if mark: ভুল — সেখানে if mark is not None: লিখুন। is None (পর্ব ০৮): None একটিই object, তাই পরিচয় (identity) যাচাই; == কোনো class নিজের মতো বদলে দিতে পারে। in দিয়ে membership — একাধিক or-এর বদলে। chained comparison গণিতের মতোই পড়া যায়। আর any/all (পর্ব ১৮) “কোনো একটি কি…?” / “সবগুলো কি…?” প্রশ্নের সরাসরি উত্তর — প্রথম উত্তর পেলেই থেমে যায়।

# আগে
for i in range(len(names)):
    print(i + 1, names[i])
for i in range(len(names)):
    print(names[i], marks[i])
squares = []
for n in nums:
    if n % 2 == 0:
        squares.append(n * n)

# পরে
for roll, name in enumerate(names, start=1):
    print(roll, name)
for name, mark in zip(names, marks, strict=True):
    print(name, mark)
squares = [n * n for n in nums if n % 2 == 0]

পর্ব ১২-এর enumerate সূচক আর মান দুটিই দেয় — start=1 দিলে রোল নম্বরের মতো ১ থেকে। zip দুটি list পাশাপাশি হাঁটে; Python 3.10 থেকে strict=True দিলে দৈর্ঘ্য না মিললে চুপচাপ ছোটটায় থেমে না গিয়ে ValueError দেয় — লুকানো ভুল ধরার সহজ উপায়। পর্ব ১৮-এর list comprehension “একটি list থেকে আরেকটি list বানানো”-র সবচেয়ে স্পষ্ট রূপ। তবে comprehension-এ তিন স্তর লুপ আর দুটি শর্ত ঢুকে গেলে সাধারণ লুপই ভালো — পাঠযোগ্যতাই মাপকাঠি। আর শুধু কোনো কাজ করানোর জন্য (যেমন print) comprehension লিখবেন না; সেটি list বানানোর জন্য।

# আগে                                  # পরে
temp = a; a = b; b = temp              a, b = b, a
first = row[0]; rest = row[1:]         first, *rest = row
if key in stock:                       qty = stock.get(key, 0)
    qty = stock[key]
else:
    qty = 0
if area not in groups:                 groups = defaultdict(list)
    groups[area] = []                  groups[area].append(shop)
groups[area].append(shop)
msg = "নাম: " + name + ", বয়স: " + str(age)   msg = f"নাম: {name}, বয়স: {age}"
# নিজে লেখা bubble sort (১২ লাইন)       ranked = sorted(students, key=lambda s: s.gpa)

পর্ব ১৫-এর unpacking দিয়ে অদলবদল আর ভাগ করা এক লাইনে। পর্ব ১৬-এর dict.get(key, default), setdefault আর defaultdict “চাবি না থাকলে কী হবে” প্রশ্নটি পরিষ্কারভাবে সামলায়। পর্ব ১০-এর f-string যোগ-চিহ্ন আর str()-এর জঙ্গল সরায়। আর সাজাতে sorted(..., key=...) — পর্ব ৩৮-এর key function বলে দেয় কী দেখে সাজাতে হবে; নিজের তুলনা-ফাংশন লিখতে হয় না।

# আগে
f = open("marks.csv", encoding="utf-8")
data = f.read()
f.close()                       # মাঝে error হলে বন্ধই হয় না
if os.path.exists(path) and os.path.isfile(path):
    size = os.path.getsize(os.path.join(folder, "a.txt"))

# পরে
with open("marks.csv", encoding="utf-8") as f:
    data = f.read()             # যা-ই ঘটুক, ফাইল বন্ধ হবে
try:
    size = (folder / "a.txt").stat().st_size
except FileNotFoundError:
    size = 0

পর্ব ৩৬-এর with নিশ্চিত করে ফাইল বা connection বন্ধ হবে, মাঝে exception এলেও। পর্ব ২৫-এর pathlib-এ পথ একটি object — / দিয়ে জোড়া, .stat(), .read_text(); os.path-এর স্ট্রিং-জোড়ার চেয়ে পরিষ্কার। আর পর্ব ২৪-এর EAFP: আগে অনুমতি না চেয়ে কাজটি করে ফেলুন, ব্যর্থ হলে নির্দিষ্ট exception ধরুন। “আছে কি না দেখে তারপর খোলা” (LBYL) পদ্ধতিতে দেখা আর খোলার মাঝখানে ফাইল মুছে যেতে পারে; EAFP-তে সেই ফাঁক নেই।

# আগে — mutable default আর গভীর nesting
def add_item(item, cart=[]):
    cart.append(item)
    return cart

def discount(order):
    if order.paid:
        if order.total > 1000:
            if not order.used_coupon:
                return order.total * 0.1
    return 0

# পরে
def add_item(item, cart=None):
    cart = [] if cart is None else cart
    cart.append(item)
    return cart

def discount(order):
    if not order.paid or order.used_coupon:
        return 0
    if order.total <= 1000:
        return 0
    return order.total * 0.1

পর্ব ২০-এর mutable default-এর ফাঁদ: cart=[] একবারই তৈরি হয়, তাই সব ডাক একই list ভাগ করে — আগের গ্রাহকের পণ্য পরের গ্রাহকের কার্টে! None দিয়ে শুরু করে ভেতরে নতুন list বানান। আর early return (বা guard clause): বাতিলের শর্তগুলো আগে সামলে বেরিয়ে যান, যাতে মূল কাজ বাঁ দিকে, এক স্তরে থাকে। তিন স্তর গভীর if পড়তে মাথায় তিনটি শর্ত একসাথে ধরে রাখতে হয়; guard clause-এ একবারে একটি।

Idiom মানে অন্ধ নিয়ম নয়। if items: ঠিক, যদি items সবসময় একটি list হয়। কিন্তু যদি সেটি None-ও হতে পারে আর “খালি list” ও “কোনো list নেই” দুটির অর্থ আলাদা হয়, তাহলে দুটি আলাদা করে যাচাই করুন। একইভাবে, comprehension-এর ভেতরে walrus অপারেটর, তিনটি for আর lambda একসাথে ঠেসে দিলে সেটি আর “Pythonic” থাকে না — চালাক হয়ে যায়।

৫ · একটি ফাংশন, পাঁচটি idiom — ধাপে ধাপে refactor

পর্ব ২১-এর মাঝপথের প্রজেক্টের মতো একটি ফলাফল-স্ক্রিপ্টের কথা ভাবুন। কেউ একজন — হয়তো C থেকে আসা, হয়তো তাড়াহুড়োয় — একটি ফাংশন লিখেছেন: শিক্ষার্থীদের নাম আর নম্বরের দুটি list নেয়, আর যাঁদের নম্বর ৪০ থেকে ১০০-র মধ্যে, তাঁদের "নাম: নম্বর" লেখার list ফেরত দেয়। ফাংশনটি ১৫ লাইনের, আর ঠিকঠাক চলে।

এখন একে Pythonic বানাব। নিয়ম একটাই: প্রতিটি ধাপে একটিমাত্র বদল, আর প্রতিটি ধাপের পরে আচরণ হুবহু একই. একেই বলে refactoring — বাইরের আচরণ না বদলে ভেতরের গঠন উন্নত করা। একসাথে সব বদলালে কোথাও ভুল ঢুকলে খুঁজে পাওয়া কঠিন; এক ধাপে এক বদল হলে প্রতিটি ধাপ যাচাই করা সহজ।

ধাপ ১ · zip১৫ → ১১ লাইন
def GetPassedList(names, marks):
    result = []
    # মুছে গেল: i = 0 / while i < len(names): / name = names[i]
    #           mark = marks[i] / i = i + 1
    for name, mark in zip(names, marks, strict=True):
        if mark >= 40:
            if mark <= 100:
                line = name + ": " + str(mark)
                result.append(line)
    if len(result) == 0:
        return []
    else:
        return result
ধাপ ২ · chained comparison১১ → ১০ লাইন
def GetPassedList(names, marks):
    result = []
    for name, mark in zip(names, marks, strict=True):
        if 40 <= mark <= 100:
            line = name + ": " + str(mark)
            result.append(line)
    if len(result) == 0:
        return []
    else:
        return result
ধাপ ৩ · f-string১০ → ৯ লাইন
def GetPassedList(names, marks):
    result = []
    for name, mark in zip(names, marks, strict=True):
        if 40 <= mark <= 100:
            result.append(f"{name}: {mark}")
    if len(result) == 0:
        return []
    else:
        return result
ধাপ ৪ · অকারণ শাখা বাদ৯ → ৬ লাইন
def GetPassedList(names, marks):
    result = []
    for name, mark in zip(names, marks, strict=True):
        if 40 <= mark <= 100:
            result.append(f"{name}: {mark}")
    return result
    # খালি হলে result নিজেই [] — আলাদা শাখার দরকার ছিল না
ধাপ ৫ · comprehension আর নামচূড়ান্ত
PASS_MARK, MAX_MARK = 40, 100


def passed_students(names, marks):
    """পাসের সীমার ভেতরের শিক্ষার্থীদের 'নাম: নম্বর' list."""
    return [
        f"{name}: {mark}"
        for name, mark in zip(names, marks, strict=True)
        if PASS_MARK <= mark <= MAX_MARK
    ]
  1. Index লুপ → zip. মূল কোডে ছিল i = 0, while i < len(names):, দুটি সূচক-পড়া লাইন আর শেষে i = i + 1 — পাঁচটি লাইন শুধু “দুটি list পাশাপাশি হাঁটা”-র জন্য, আর i = i + 1 ভুলে গেলে infinite loop. zip একাই সেই কাজ করে। strict=True একটি বাড়তি সুরক্ষা: দুটি list-এর দৈর্ঘ্য না মিললে পুরোনো কোড হয় IndexError দিত, নয়তো চুপচাপ কিছু নাম বাদ দিত; এখন স্পষ্ট ValueError.
  2. দুই স্তরের if → একটি chained comparison. if mark >= 40:-এর ভেতরে if mark <= 100: মানে আসলে “৪০ থেকে ১০০-র মধ্যে”. Python-এ সেটি গণিতের খাতার মতোই লেখা যায়: 40 <= mark <= 100. এক স্তর nesting কমল। (ruff-এর SIM102 নিয়ম এই ধরনের nested if নিজেই ধরিয়ে দেয়।)
  3. যোগ-চিহ্ন আর str() → f-string. name + ": " + str(mark)-এ তিনটি টুকরো, একটি রূপান্তর আর একটি অস্থায়ী ভ্যারিয়েবল line. f-string-এ পুরো লেখাটি যেমন দেখাবে, তেমনই লেখা। অস্থায়ী ভ্যারিয়েবলটিও আর লাগে না।
  4. অকারণ শাখা বাদ. if len(result) == 0: return [] — কিন্তু result খালি হলে সেটি নিজেই []! চার লাইন একটি return result-এ নামল। এ ধরনের “কিছুই না করা” শাখা প্রায়ই আসে আগের কোনো সংস্করণ থেকে, যার কারণ আর নেই। (যদি খালি হলে None ফেরত দেওয়ার ইচ্ছা থাকত, সেটি হতো ভিন্ন আচরণ — refactoring নয়।)
  5. লুপ-আর-append → comprehension; সাথে নাম. “খালি list বানাও, লুপে শর্ত মিললে যোগ করো, ফেরত দাও” — এটিই list comprehension-এর সংজ্ঞা। সাথে PEP 8-এর নাম: GetPassedList → passed_students (snake_case; “Get” বাদ, কারণ ফাংশন মানেই কিছু দেওয়া), আর জাদুর সংখ্যা ৪০ ও ১০০ এখন নামসহ ধ্রুবক। একটি docstring বলে দেয় ফাংশন কী ফেরত দেয়। ফাংশনের ভেতরে আর একটিও অস্থায়ী ভ্যারিয়েবল নেই।

দাবি করা সহজ যে “আচরণ একই আছে”; প্রমাণ করুন। নিচের প্রোগ্রামে আগের আর পরের দুটি ফাংশনই আছে, একই ডেটায় চালিয়ে ফল মেলানো হয়েছে:

def GetPassedList(names, marks):
    result = []
    i = 0
    while i < len(names):
        name = names[i]
        mark = marks[i]
        if mark >= 40:
            if mark <= 100:
                line = name + ": " + str(mark)
                result.append(line)
        i = i + 1
    if len(result) == 0:
        return []
    else:
        return result


PASS_MARK, MAX_MARK = 40, 100


def passed_students(names, marks):
    """পাসের সীমার ভেতরের শিক্ষার্থীদের 'নাম: নম্বর' list."""
    return [
        f"{name}: {mark}"
        for name, mark in zip(names, marks, strict=True)
        if PASS_MARK <= mark <= MAX_MARK
    ]


names, marks = ["Rafi", "Mitu", "Sabbir", "Nadia"], [72, 35, 104, 40]
print(passed_students(names, marks))
print(GetPassedList([], []), passed_students([], []))
print("একই ফল?", GetPassedList(names, marks) == passed_students(names, marks))

আউটপুট তিন লাইন: ['Rafi: 72', 'Nadia: 40'], তারপর [] [], তারপর একই ফল? True. Mitu-র ৩৫ সীমার নিচে, Sabbir-এর ১০৪ ওপরে (সম্ভবত ডেটা-এন্ট্রির ভুল), তাই দুজনই বাদ; Nadia-র ঠিক ৪০ — <= থাকায় অন্তর্ভুক্ত। দ্বিতীয় লাইন প্রমাণ করে খালি ইনপুটে দুটিই [] দেয় — ধাপ ৪-এর দাবি সত্য। বাস্তব প্রকল্পে এই মিলিয়ে দেখা হতো পর্ব ৩৯-এর মতো একটি pytest test-এ, যা refactor শুরুর আগে লেখা। আরেকটি কথা: ফাংশনের মাঝে দুই ফাঁকা লাইনের মতো চেহারার বিষয় formatter নিজেই বসিয়ে দেয়; কিন্তু আসল সমস্যাগুলো (index লুপ, nested if, অকারণ শাখা) কোনো formatter ঠিক করে না — সেগুলো মানুষের বা linter-এর কাজ।

চালাক বনাম স্পষ্ট — একটি এক-লাইনারের গল্প

উল্টো দিকের ভুলও আছে: “ছোট মানেই Pythonic” ভেবে সব এক লাইনে লেখা। একটি দোকানের পণ্য-বিবরণের লেখা থেকে সবচেয়ে বেশি আসা তিনটি শব্দ বের করার এই এক-লাইনারটি দেখুন — বুদ্ধিদীপ্ত, কিন্তু পড়তে গেলে থামতে হয়:

ধাপ ১“চালাক” রূপ
top = sorted({w: text.split().count(w) for w in set(text.split())}
             .items(), key=lambda p: -p[1])[:3]
ধাপ ২ভেতর থেকে বাইরে পড়া
top = sorted({w: text.split().count(w) for w in set(text.split())}
             .items(), key=lambda p: -p[1])[:3]
# ① set(...) — অনন্য শব্দ   ② প্রতিটির জন্য আবার split + count
# ③ dict → items → উল্টো ক্রমে sort   ④ প্রথম তিনটি
ধাপ ৩লুকানো খরচ
# n শব্দ, k অনন্য শব্দ
for w in set(words):        # k বার
    words.count(w)          # প্রতিবার পুরো n শব্দ গোনা
# মোট প্রায় k × n — দশ হাজার শব্দে কোটির ঘরে
ধাপ ৪স্পষ্ট রূপ
from collections import Counter

words = text.split()
top = Counter(words).most_common(3)
ধাপ ৫সমান গণনা হলে কোন শব্দ আগে?
# চালাক রূপ: set-এর ক্রম নিশ্চিত নয় → সমান হলে ফল
#            একেক রান-এ একেক রকম হতে পারে
# Counter.most_common: সমান হলে প্রথম দেখার ক্রমে —
#            একই ইনপুটে সবসময় একই ফল
  1. দেখতে চমৎকার, পড়তে কঠিন। একটি dict comprehension, একটি set, sorted, একটি lambda, slicing — সব এক বাক্যে। এটি সঠিক ফল দেয়। কিন্তু Zen-এর ১৭ নম্বর নীতি মনে করুন: বোঝাতে কষ্ট হলে খারাপ ভাবনা। Code review-তে এটি বোঝাতে আপনাকে ধাপ ২-এর মতো ভেঙে বলতে হবে।
  2. ভেতর থেকে বাইরে। এমন কোড পড়তে হয় উল্টো দিক থেকে — সবচেয়ে ভেতরের অংশ আগে। আর খেয়াল করলে দেখবেন text.split() দুবার লেখা, তার একটি আবার comprehension-এর ভেতরে, অর্থাৎ প্রতিটি অনন্য শব্দের জন্য একবার করে পুরো লেখা নতুন করে ভাঙা হচ্ছে।
  3. লুকানো O(n²)-এর মতো খরচ। পর্ব ৪৩-এ দেখেছেন লুপের ভেতরে O(n) কাজ কী করে। list.count প্রতিবার পুরো list গোনে। k অনন্য শব্দ হলে মোট প্রায় k × n ধাপ। চালাক কোড প্রায়ই এভাবেই ধীর কোড — কারণ খরচ এক লাইনে চাপা পড়ে চোখ এড়ায়।
  4. স্পষ্ট রূপ — স্ট্যান্ডার্ড লাইব্রেরি। পর্ব ২৭-এর Counter ঠিক এই কাজের জন্যই তৈরি: একবার হেঁটে সব গোনে (O(n)), আর most_common(3) নামটিই বলে দেয় কী হচ্ছে। তিন লাইন, কিন্তু প্রতিটি লাইন পড়ামাত্র বোঝা যায়। “Simple is better than complex” — আর এখানে simple দ্রুতও।
  5. সূক্ষ্ম পার্থক্য: সমান গণনা। দুটি শব্দ সমান সংখ্যকবার এলে চালাক রূপে কোনটি আগে আসবে, তা নির্ভর করে set-এর ভেতরের ক্রমের ওপর — যা string-এর hash-এর কারণে প্রতিবার প্রোগ্রাম চালালে বদলাতে পারে। most_common সমান হলে প্রথম দেখার ক্রম রাখে, তাই ফল নির্ভরযোগ্য। স্পষ্ট কোড শুধু পড়তে সহজ নয়, তার আচরণও অনুমানযোগ্য।

৬ · নাম, docstring, comment আর type hint

ভালো নাম — অর্ধেক documentation

“প্রোগ্রামিংয়ের সবচেয়ে কঠিন দুটি কাজের একটি হলো নাম দেওয়া” — এই রসিকতা প্রোগ্রামারদের মধ্যে বহুদিনের। কয়েকটি সহজ নিয়ম অনেকটা পথ এগিয়ে দেয়:

  • ফাংশন = ক্রিয়া, ডেটা = বিশেষ্য: calculate_vat(), send_reminder(), load_students(); ডেটা students, vat_rate, due_date. True/False ফেরত দেওয়া ফাংশন বা bool ভ্যারিয়েবল প্রশ্নের মতো: is_paid, has_discount, can_vote().
  • বহুবচনে সংগ্রহ: for student in students: নিজেই একটি বাক্য। for x in lst: কিছুই বলে না।
  • একক থাকলে মান নয়, অর্থ: timeout_seconds = 30, price_taka — timeout = 30 দেখে বোঝা যায় না ৩০ সেকেন্ড না মিলিসেকেন্ড।
  • টাইপ নামে লিখবেন না: str_name, lst_marks, dict_stock — এটি Hungarian notation-এর ধাঁচ, যা পুরোনো C/Windows কোডে প্রচলিত ছিল। Python-এ type বদলাতে পারে (list থেকে tuple), তখন নামটি মিথ্যা বলে; আর type জানানোর আসল জায়গা type hint.
  • সংক্ষেপ এড়িয়ে চলুন, শুধু সর্বজনীনগুলো ছাড়া (url, id, html, লুপে i). cust_addr_ln2 নয়, customer_address_line2. আর ফেলে দেওয়া মানের জন্য পর্ব ১৫-এর _: for _ in range(3):.

Docstring — ফাংশন কী করে, কাকে কী দিতে হবে

পর্ব ০৪-এ docstring-এর পরিচয় পেয়েছেন। এর নিয়ম আছে আলাদা একটি PEP-এ — PEP 257: তিনটি ডাবল কোট ("""), প্রথম লাইনে এক বাক্যের সারাংশ (আদেশসূচক: “Return…”, “ফেরত দেয়…”), দরকার হলে একটি ফাঁকা লাইন আর তারপর বিস্তারিত। প্রতিটি public module, class, ফাংশন আর method-এ docstring থাকা উচিত। help(func), VS Code-এর hover আর documentation-টুল সবাই এটিই দেখায়।

def monthly_installment(principal: float, annual_rate: float, months: int) -> float:
    """Return the fixed monthly installment (EMI) for a loan.

    annual_rate is a fraction, e.g. 0.09 for 9%. Raises ValueError
    if months is not positive.
    """
    if months <= 0:
        raise ValueError("months must be positive")
    r = annual_rate / 12          # সূত্রে মাসিক হার লাগে
    if r == 0:
        return principal / months
    return principal * r / (1 - (1 + r) ** -months)

Comment — “কী” নয়, “কেন”

ভালো নাম আর পরিষ্কার কোড নিজেই বলে “কী” হচ্ছে। comment-এর কাজ যা কোড বলতে পারে না, তা বলা — কেন. তুলনা করুন:

  • i += 1 # i এক বাড়াও — অকেজো; কোডই এটা বলছে।
  • retries = 5 # bKash-এর sandbox মাঝে মাঝে প্রথম অনুরোধ ফেলে দেয় — মূল্যবান; এই তথ্য কোড থেকে কোনোদিন জানা যেত না।
  • # Bangla Unicode-এ ‘য়’ দুইভাবে লেখা যায়; তুলনার আগে NFC করছি — পরের প্রোগ্রামারকে একটি রাত বাঁচিয়ে দেয়।

আরেকটি নিয়ম: কোড বদলালে comment-ও বদলান। ভুল comment কোনো comment না থাকার চেয়ে খারাপ। আর কোড comment করে রেখে দেবেন না (# old_total = ...) — পুরোনো রূপ git-এর ইতিহাসে আছে।

Public API-তে type hint

পর্ব ৩৭-এ দেখেছেন type hint চলার সময় কিছু করে না, কিন্তু পাঠক আর টুলকে চুক্তিটা জানায়। কোথায় দেবেন? সবচেয়ে বেশি লাভ public API-তে — যে ফাংশন অন্য module বা অন্য মানুষ ডাকবে: তার argument আর return-এর type. উপরের monthly_installment-এর signature পড়েই বোঝা যায় কী দিতে হবে আর কী পাবেন; docstring-এ আর টাইপ লেখার দরকার নেই। ছোট local ভ্যারিয়েবলে hint সাধারণত অপ্রয়োজনীয় — count: int = 0 পাঠককে নতুন কিছু বলে না।

৭ · টুল স্টাইল সামলাক — ruff, black, isort, mypy আর pre-commit

PEP 8-এর প্রতিটি নিয়ম মনে রেখে প্রতিটি লাইনে মানা — আর code review-তে “এখানে কমার পরে space দিন” লিখে সময় নষ্ট করা — দুটোই অপচয়। এই কাজ যন্ত্রের। দুই ধরনের টুল আছে:

Linter

Linter কোড না চালিয়ে পড়ে (পর্ব ৩৭-এর static analysis) আর সম্ভাব্য ভুল ও খারাপ অভ্যাস জানায়: অব্যবহৃত import, সংজ্ঞাহীন নাম, == None, bare except. কিছু সমস্যা নিজেই ঠিক করতে পারে।

ruff check, flake8, pylint

Formatter

Formatter কোডের অর্থ না বদলে চেহারা নতুন করে লেখে: space, লাইন-ভাঙা, কোট, ফাঁকা লাইন। কোনো প্রশ্ন করে না — একটিই ফলাফল।

ruff format, black

Import sorter

অংশ ৩-এর তিন দল আর বর্ণানুক্রম স্বয়ংক্রিয়ভাবে।

isort, ruff-এর “I” নিয়ম

Type checker

type hint মিলিয়ে দেখে — str-এর জায়গায় int দেওয়া, None হতে পারে এমন মানে method ডাকা।

mypy, pyright

ruff — একটি টুলে প্রায় সব

বহু বছর ধরে একটি Python প্রকল্পে আলাদা আলাদা টুল লাগত: flake8 (ভুল আর PEP 8), isort (import), black (ফরম্যাট), সাথে আরও কয়েকটি plugin. প্রতিটির নিজের config, নিজের গতি। ২০২২ সালে Charlie Marsh প্রকাশ করেন ruff — Rust-এ লেখা একটি linter, যা এসব টুলের কয়েকশো নিয়ম একসাথে, আর অনেক গুণ দ্রুত চালায়। পরে এতে formatter যোগ হয়, যার লক্ষ্য black-এর সাথে প্রায় হুবহু মিল। ruff-এর পেছনের কোম্পানি Astral পরে uv-ও বানিয়েছে, যা পর্ব ২৩-এ দেখেছেন। বড় প্রকল্পে যেখানে পুরোনো টুলগুলো কয়েক মিনিট নিত, ruff প্রায়ই এক সেকেন্ডের কম নেয় — তাই প্রতিবার ফাইল সেভ করার সাথে সাথে চালানো যায়।

pip install ruff               # বা: uv add --dev ruff
ruff check .                   # সব ফাইলে lint
ruff check . --fix             # নিরাপদ সমাধানগুলো নিজেই করে দাও
ruff format .                  # ফরম্যাট করো (black-ধাঁচে)
ruff format --check .          # শুধু দেখাও, বদলিও না (CI-তে)

একটি দোকানের হিসাবের অগোছালো ফাইলে ruff কী বলে আর কী করে, ধাপে ধাপে দেখুন:

ধাপ ১shop.py — চলে, কিন্তু অগোছালো
import os, sys
import json
from math import *

def calc_total(items,discount = None):
    total=0
    for i in range(len(items)):
        total = total+items[i]["price"]*items[i]["qty"]
    if discount != None:
        total = total - discount
    try:
        rate = float(os.environ["VAT"])
    except:
        rate = 0.15
    unused = 42
    return total*(1+rate)
ধাপ ২$ ruff check shop.py (সংক্ষিপ্ত রূপ)
shop.py:1:1: E401 [*] Multiple imports on one line
shop.py:1:1: I001 [*] Import block is un-sorted or un-formatted
shop.py:1:12: F401 [*] `sys` imported but unused
shop.py:2:8: F401 [*] `json` imported but unused
shop.py:3:1: F403 `from math import *` used; unable to detect undefined names
shop.py:9:20: E711 Comparison to `None` should be `cond is not None`
shop.py:13:5: E722 Do not use bare `except`
shop.py:15:5: F841 Local variable `unused` is assigned to but never used
Found 8 errors.
[*] 4 fixable with `--fix` (2 unsafe fixes available ...)
ধাপ ৩$ ruff check --fix shop.py
import os
from math import *


def calc_total(items,discount = None):
    total=0
    ...
Found 8 errors (4 fixed, 4 remaining).
ধাপ ৪$ ruff format shop.py
def calc_total(items, discount=None):
    total = 0
    for i in range(len(items)):
        total = total + items[i]["price"] * items[i]["qty"]
    if discount != None:
        ...
    return total * (1 + rate)
1 file reformatted
ধাপ ৫মানুষের কাজ — বাকি ৪টি আর যুক্তি
import os

VAT_FALLBACK = 0.15


def calc_total(items, discount=None):
    total = sum(item["price"] * item["qty"] for item in items)
    if discount is not None:
        total -= discount
    try:
        rate = float(os.environ["VAT"])
    except (KeyError, ValueError):
        rate = VAT_FALLBACK
    return total * (1 + rate)
$ ruff check shop.py  →  All checks passed!
  1. অগোছালো, কিন্তু চলে। এক লাইনে দুটি import, দুটি অব্যবহৃত, একটি star import, != None, bare except, কোথাও ব্যবহার না হওয়া unused, আর অসমান space. Python কোনো আপত্তি করে না। ভুলগুলো ধরা পড়বে একদিন production-এ — যেমন bare except একটি টাইপো-জনিত NameError-কেও চুপচাপ গিলে ফেলবে।
  2. ruff check — রিপোর্ট। প্রতিটি লাইনে ফাইল:লাইন:কলাম, নিয়মের কোড আর ব্যাখ্যা। কোডের প্রথম অক্ষর উৎস বলে: E = pycodestyle (PEP 8), F = pyflakes (আসল ভুল), I = isort. [*] চিহ্ন মানে ruff নিজেই নিরাপদে ঠিক করতে পারে। খেয়াল করুন, I নিয়ম ruff-এর default তালিকায় নেই; নিচের pyproject.toml-এর মতো select-এ যোগ করলে তবেই I001 আসে। কোনো নিয়ম না বুঝলে ruff rule E711 লিখুন — পুরো ব্যাখ্যা, কেন খারাপ, উদাহরণসহ।
  3. --fix — নিরাপদ সমাধান। ruff চারটি [*] সমস্যা ঠিক করল: import আলাদা লাইনে, অব্যবহৃত sys আর json বাদ, import ব্লকের পরে দুই ফাঁকা লাইন। বাকি চারটি সে ছোঁয়নি। E711 আর F841-এর সমাধান “unsafe” — আচরণ বদলাতে পারে (কোনো class == বদলে রাখলে is not-এ ফল ভিন্ন হতে পারে), তাই --unsafe-fixes না বললে করে না।
  4. ruff format — চেহারা। কমার পরে space, keyword-এর default-এ =-এর পাশে space নেই, অপারেটরের দুই পাশে space. অর্থ এক বিন্দুও বদলায়নি — শুধু চেহারা। formatter কখনো != None ঠিক করে না, range(len())-ও না; সেটি তার কাজ নয়।
  5. বাকিটা আপনার। star import বাদ (math-এর কিছুই লাগছিল না), is not None, bare except-এর বদলে নির্দিষ্ট দুটি exception (চাবি নেই, বা সংখ্যা নয়), অব্যবহৃত ভ্যারিয়েবল বাদ। আর এমন উন্নতি, যা কোনো নিয়ম ধরেনি: range(len()) লুপ → sum আর generator, জাদুর সংখ্যা ০.১৫ → নামসহ ধ্রুবক। টুল যান্ত্রিক কাজ শেষ করেছে; বিচার-বুদ্ধির কাজ আপনার।

pyproject.toml-এ নিয়ম লিখে রাখা

প্রতিটি মানুষ নিজের মেশিনে আলাদা নিয়মে টুল চালালে লাভ নেই। পর্ব ২৩-এর pyproject.toml-এ নিয়ম লিখে রাখুন — তাহলে প্রকল্পের সবার, আর CI-এর, একই নিয়ম:

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
# E/W = pycodestyle, F = pyflakes, I = isort, B = bugbear (সম্ভাব্য বাগ),
# UP = pyupgrade (পুরোনো ধাঁচ → আধুনিক), SIM = সরল করার পরামর্শ
select = ["E", "W", "F", "I", "B", "UP", "SIM"]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["B011"]

ruff-এর নিজের default নিয়মের তালিকা সময়ের সাথে বদলেছে, তাই select স্পষ্ট লিখে রাখাই নিরাপদ। নতুন প্রকল্পে ছোট তালিকা দিয়ে শুরু করুন, পরে ধীরে ধীরে বাড়ান। কোনো একটি লাইনে নিয়ম ভাঙা সত্যিই দরকার হলে সেই লাইনের শেষে noqa comment দিন — # noqa: F401 — আর পাশে কারণ লিখুন, যেমন Zen-এর প্রোগ্রামে করেছি। ঢালাওভাবে # noqa লেখা টুলের মুখ বন্ধ করা মাত্র।

VS Code-এ

Astral-এর অফিসিয়াল “Ruff” extension বসিয়ে settings-এ format on save চালু করুন, আর Python-এর default formatter হিসেবে Ruff বেছে নিন। তাহলে প্রতিবার Ctrl+S চাপলেই ফাইল ফরম্যাট হবে, আর ভুলের নিচে ঢেউ-দাগ পড়বে লেখার সময়েই।

black, isort, flake8, pylint — ইতিহাস আর আজকের জায়গা

  • pylint (২০০৩ থেকে): সবচেয়ে পুরোনো আর সবচেয়ে কড়া linter-গুলোর একটি; অনেক গভীর বিশ্লেষণ করে (যেমন অনেক বেশি argument-ওয়ালা ফাংশন), কিন্তু ধীর আর শুরুতে প্রচুর সতর্কবার্তা দেয়। এখনো অনেক প্রকল্পে চলে, বিশেষত ruff যা ধরে না এমন কিছু নিয়মের জন্য।
  • flake8 (২০১০ থেকে): তিনটি টুল এক মোড়কে — pyflakes (আসল ভুল), pycodestyle (PEP 8) আর mccabe (জটিলতা)। plugin-এর বিশাল জগৎ। ruff-এর E, F, B ইত্যাদি কোড এসেছে এখান থেকেই।
  • isort: import সাজানোর বিশেষ টুল; ruff-এর I নিয়ম একই কাজ করে।
  • black (২০১৮ থেকে): “The uncompromising code formatter” — প্রায় কোনো সেটিং নেই, ইচ্ছা করেই। ruff format-এর নকশা black-কে অনুসরণ করে। কেন এই “আপসহীনতা” এত জনপ্রিয় হলো, ভাবনার প্রশ্ন ২-এ।
  • mypy: পর্ব ৩৭-এর type checker. ruff type যাচাই করে না, তাই দুটি পাশাপাশি চলে।

pre-commit — ভুল ঢোকার আগেই থামানো

টুল থাকলেই হয় না, চালাতে মনে রাখতে হয়। git hook হলো git-এর একটি ব্যবস্থা, যা নির্দিষ্ট সময়ে (যেমন commit-এর ঠিক আগে) একটি স্ক্রিপ্ট চালায়। pre-commit নামের টুল এই hook সহজে সাজাতে দেয়: প্রকল্পে একটি .pre-commit-config.yaml রাখুন —

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.x.y            # ruff-এর যে version ব্যবহার করবেন
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

তারপর একবার pip install pre-commit আর pre-commit install. এখন থেকে প্রতিটি git commit-এর আগে ruff চলবে; সমস্যা থাকলে commit আটকে যাবে আর ruff যা পারে ঠিক করে দেবে। একই যাচাই CI-তেও চালান (পর্ব ৩৯-এর continuous integration) — কেউ hook বাদ দিয়ে commit করলেও pull request-এ ধরা পড়বে।

৮ · Anti-pattern আর code review

Anti-pattern হলো এমন সমাধান, যা শুরুতে স্বাভাবিক মনে হয় কিন্তু বারবার সমস্যা তৈরি করে। এগুলো চেনা থাকলে নিজের কোডে, অন্যের কোডে আর AI-এর কোডে দ্রুত ধরতে পারবেন। পর্ব ৩১-এর code smell-এর মতোই — গন্ধ পেলে কাছে গিয়ে দেখুন।

Anti-patternসমস্যাPythonic বিকল্প
Java-ধাঁচের get_name()/set_name()অকারণ boilerplate; Python-এ attribute সরাসরি পড়া স্বাভাবিকসাধারণ attribute; পরে যাচাই লাগলে পর্ব ৩২-এর property — ডাকার কোড বদলাতে হয় না
bare except: বা except Exception: passভুল গিলে ফেলা — টাইপো চাপা পড়ে; bare except-এ Ctrl+C পর্যন্তনির্দিষ্ট exception ধরুন; সত্যিই সব ধরতে হলে log করে আবার raise
from module import *নাম কোথা থেকে এল অজানা; নাম ঢেকে যায়import module বা নির্দিষ্ট নাম import
গভীর nesting (৪-৫ স্তর if/for)পড়তে একসাথে অনেক শর্ত মাথায় রাখতে হয়guard clause, ছোট ফাংশনে ভাগ, continue
বিশাল ফাংশন (২০০ লাইন, দশটি কাজ)test করা, পুনর্ব্যবহার, বোঝা — সবই কঠিনপ্রতিটি ফাংশন একটি কাজ; নামটি যদি “এবং” ছাড়া বলা না যায়, ভাগ করুন
Magic number: if mark < 33, * 0.15৩৩ কী? বদলাতে হলে কত জায়গায়?PASS_MARK = 33, VAT_RATE = 0.15 — এক জায়গায় নাম ও মান
type(x) == listsubclass আর duck typing ভাঙেisinstance(x, list), বা পর্ব ৩৩-এর duck typing

বাস্তব উদাহরণ: দোকানের স্টক ফাংশন পরিষ্কার করা

পুরান ঢাকার একটি পাইকারি দোকানের স্টক থেকে “যে পণ্য প্রায় শেষ” তার তালিকা বানানোর একটি ফাংশন, আগে আর পরে:

# আগে
def checkStock(d, lst=[]):
    for k in d.keys():
        if d[k]["qty"] < 10:
            if d[k]["active"] == True:
                lst.append(k + " (" + str(d[k]["qty"]) + " left)")
    return lst

# পরে
LOW_STOCK_LIMIT = 10


def low_stock_report(stock: dict[str, dict]) -> list[str]:
    """Return 'name (qty left)' for active products below the limit."""
    return [
        f"{name} ({item['qty']} left)"
        for name, item in stock.items()
        if item["active"] and item["qty"] < LOW_STOCK_LIMIT
    ]

এক নজরে বদলগুলো: camelCase নাম → snake_case আর ক্রিয়াবাচক নাম; d, lst, k → stock, name, item; সবচেয়ে বিপজ্জনক — lst=[] mutable default, যার কারণে দ্বিতীয়বার ডাকলে আগের তালিকাও আবার আসত (দোকানদার ভাবতেন একই পণ্য দুবার কম!); .keys() আর d[k] বারবার → .items(); == True → সরাসরি; দুই স্তর if → and; জাদুর ১০ → ধ্রুবক; যোগ-চিহ্ন → f-string; আর public ফাংশনে type hint ও docstring.

Code review — কী দেখবেন, কীভাবে বলবেন

Code review মানে কোড মূল শাখায় যাওয়ার আগে অন্য একজনের চোখ বুলানো — সাধারণত একটি pull request-এ। Formatter আর linter যখন স্টাইল সামলায়, তখন reviewer-এর সময় যায় আসল প্রশ্নে: যুক্তি ঠিক আছে কি? edge case? নাম কি অর্থ বলে? test আছে কি? ওপরের checkStock একটি pull request-এ এলে একজন ভালো reviewer-এর মন্তব্য দেখতে এরকম:

  1. [জরুরি] lst=[] default — ফাংশন দ্বিতীয়বার ডাকলে আগের ফল জমে থাকবে। None দিয়ে শুরু করা, বা list ফেরত দেওয়া comprehension কি চলবে?
  2. [জরুরি] কোনো পণ্যে "active" চাবি না থাকলে KeyError. পুরোনো ডেটায় এমন পণ্য আছে কি? থাকলে item.get("active", False).
  3. [পরামর্শ] ১০ সংখ্যাটির একটি নাম দিলে পরে সীমা বদলানো সহজ হবে — সম্ভবত পণ্যভেদে আলাদা সীমাও লাগবে?
  4. [পরামর্শ] for name, item in stock.items() লিখলে d[k] তিনবার লিখতে হয় না।
  5. [প্রশ্ন] ঠিক ১০টি থাকলে কি “প্রায় শেষ”? এখন < 10 — ইচ্ছাকৃত হলে docstring-এ লিখে দিন।
  6. [ছোট] একটি test যোগ করবেন? খালি স্টক, inactive পণ্য আর ঠিক সীমায় থাকা পণ্য — তিনটি ঘটনা।
Review-এর আদব

(১) কোড নিয়ে বলুন, মানুষ নিয়ে নয় — “এই ফাংশনে…”, “আপনি সবসময়…” নয়। (২) কারণ দিন — “এটা খারাপ” নয়, “এতে দ্বিতীয় ডাকে ফল জমে যায়”. (৩) গুরুত্ব চিহ্নিত করুন — জরুরি, পরামর্শ, প্রশ্ন, ছোট — যাতে লেখক জানেন কোনটি না করলে merge হবে না। (৪) প্রশ্নের রূপে বলুন, যেখানে আপনি নিশ্চিত নন — হয়তো লেখক এমন কিছু জানেন যা আপনি জানেন না। (৫) ভালো জিনিসের প্রশংসাও করুন। (৬) টুল যা ধরতে পারে, তা নিয়ে মন্তব্য করবেন না — টুলটা চালু করুন। আর লেখক হিসেবে: মন্তব্যকে আক্রমণ নয়, বিনামূল্যের শিক্ষা হিসেবে নিন; pull request ছোট রাখুন — চারশো লাইনের চেয়ে চল্লিশ লাইন ভালোভাবে review হয়।

C/C++ জানলে

C/C++-এও একই ভাবনা — কোড পড়ার যোগ্য রাখা, স্টাইল গাইড আর টুল। পার্থক্য হলো C++-এ কোনো একটি অফিসিয়াল স্টাইল গাইড নেই; Google, LLVM বা প্রকল্পের নিজস্ব গাইড চলে, আর clang-format (formatter) ও clang-tidy (linter) ruff-এর ভূমিকা পালন করে। আর C-তে for (i = 0; i < n; i++) যেখানে স্বাভাবিক idiom, Python-এ সেটির অনুবাদ range(len()) “বিদেশি উচ্চারণ” — for x in items বা enumerate-ই স্বাভাবিক। C++ টুলচেইনের পরিচয় পাবেন C++-এর টুলচেইন পর্বে।

পরীক্ষা ও ইন্টারভিউতে
  • PEP 8 কী? — Python কোডের অফিসিয়াল style guide (২০০১): নাম, indentation, লাইনের দৈর্ঘ্য, import, ফাঁকা জায়গার নিয়ম।
  • Python-এ ফাংশন, class আর ধ্রুবকের নামের ধাঁচ? — ফাংশন/ভ্যারিয়েবল snake_case, class PascalCase, ধ্রুবক UPPER_SNAKE_CASE.
  • None-এর সাথে তুলনায় is কেন, == নয়? — None একটিই object (singleton); is পরিচয় যাচাই করে, আর == কোনো class নিজের মতো বদলে রাখতে পারে।
  • Linter আর formatter-এর পার্থক্য? — linter সম্ভাব্য ভুল ও খারাপ অভ্যাস জানায় (ruff check, flake8); formatter অর্থ না বদলে শুধু চেহারা ঠিক করে (ruff format, black).
  • EAFP আর LBYL কী? — EAFP: কাজটি করে ব্যর্থ হলে exception ধরা (Pythonic); LBYL: আগে যাচাই করে তারপর কাজ — মাঝখানে অবস্থা বদলে যেতে পারে।

৯ · AI-এর যুগে — সঠিক কিন্তু অ-Pythonic কোড ধরা

AI-কে “ফলাফলের স্ক্রিপ্ট লিখে দাও” বললে যে কোড আসে, তা প্রায়ই চলে — কিন্তু প্রায়ই Python-এর মতো দেখায় না। কারণ বোঝা সহজ: AI শিখেছে কোটি কোটি লাইন কোড থেকে, যার অনেকটা C, Java আর পুরোনো Python 2 যুগের টিউটোরিয়াল। ফলে মিশ্র উচ্চারণ আসে। কয়েকটি বারবার দেখা ধরন:

AI প্রায়ই যা লেখেসমস্যাকীভাবে ধরবেন
for i in range(len(items)): আর items[i]C-ধাঁচ; off-by-one-এর সুযোগচোখে দেখা; pylint-এর consider-using-enumerate
প্রতিটি attribute-এর getter/setterJava-ধাঁচ, অকারণ কোডreview; property বা dataclass চান
অব্যবহৃত import (import os, sys, re)বিভ্রান্তিকর, ধীর শুরুruff F401, --fix
মিশ্র নাম: studentList, total_marks, GPA_calcএকই ফাইলে তিন ধাঁচruff-এর N (pep8-naming) নিয়ম, review
except Exception: pass “নিরাপত্তার জন্য”আসল ভুল চাপা পড়েruff BLE001, S110 (bare except হলে E722)
"%s" % x, .format(), os.path, typing.Listপুরোনো যুগের ধাঁচruff UP (pyupgrade) নিয়ম
প্রতিটি লাইনে “কী” বলা commentশব্দের ভিড়, “কেন” অনুপস্থিতreview — অর্থহীন comment মুছুন

ভালো খবর: এর অনেকটাই যন্ত্রে ধরা যায়। AI-এর কোড প্রকল্পে আনার আগে একটি অভ্যাস করুন — ruff check --fix আর ruff format চালান, তারপর বাকি সতর্কবার্তা পড়ুন। আর শুরুতেই AI-কে আপনার মান বলে দিন:

AI-কে Python কোড চাওয়ার সময় ধাঁচ আর যাচাই দুটিই চেয়ে নিন। নিচের কাঠামোটি কপি করে নিজের কাজ বসান:

আমি Python 3.12 ব্যবহার করি। আমার প্রকল্পে ruff চলে (select = E, W, F, I, B, UP, SIM; line-length 88) আর ruff format. কাজ: [এখানে কাজের বিবরণ — যেমন: শিক্ষার্থীদের নম্বরের CSV পড়ে প্রতিটি বিষয়ের গড় আর পাসের হার বের করো] শর্ত: ১. Pythonic Python লেখো — PEP 8-এর নাম (snake_case, class PascalCase, ধ্রুবক UPPER_CASE), range(len()) নয়, enumerate/zip, দরকার হলে comprehension, ফাইলে with আর pathlib. ২. জাদুর সংখ্যা নয় — নামসহ ধ্রুবক। ৩. bare except বা except Exception: pass নয়; শুধু নির্দিষ্ট exception ধরো। ৪. getter/setter বানাবে না; দরকার হলে dataclass বা property. ৫. public ফাংশনে type hint আর এক লাইনের docstring দাও; comment শুধু "কেন" বোঝাতে। ৬. অব্যবহৃত import রাখবে না। কোড এমন হবে যেন ruff check-এ কোনো সতর্কবার্তা না আসে। ৭. চালাক এক-লাইনারের চেয়ে স্পষ্ট কোড বেছে নাও; কোথাও জটিল কিছু করলে কেন, এক লাইনে বলো। ৮. শেষে ২-৩টি pytest test দাও, খালি ইনপুটসহ।

AI-এর লেখা Python কোড পেলে — যাচাইয়ের চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১PEP 8 কীভাবে জন্মাল, কারা লিখলেন? আর এর ভেতরে Emerson-এর “A Foolish Consistency is the Hobgoblin of Little Minds” কেন — একটি নিয়মের বইয়ে নিয়ম ভাঙার উপদেশ কেন?

Python-এর শুরুর দশকে (১৯৯০-এর দশক) কোনো আনুষ্ঠানিক স্টাইল গাইড ছিল না। Guido van Rossum নিজের একটি “Python Style Guide” প্রবন্ধ লিখেছিলেন, আর Python-এর স্ট্যান্ডার্ড লাইব্রেরিতে কাজ করা Barry Warsaw-এর ছিল নিজস্ব কিছু নির্দেশিকা। ২০০০ সালে PEP (Python Enhancement Proposal) ব্যবস্থা চালু হয় — ভাষা আর তার চর্চা নিয়ে সিদ্ধান্ত লিখে রাখার একটি আনুষ্ঠানিক পদ্ধতি। এর পরপরই, ২০০১ সালের ৫ জুলাই, এই দুটি উৎস মিলিয়ে তৈরি হয় PEP 8 — “Style Guide for Python Code”, লেখক Guido van Rossum আর Barry Warsaw. পরে আরেকজন core developer, Alyssa Coghlan (তখন Nick Coghlan নামে পরিচিত), লেখক হিসেবে যুক্ত হন; ২০১৩ সালের দিকে তাঁর হাতে গাইডটির বড় একটি হালনাগাদ হয়। লক্ষ করুন, PEP 8-এর মূল উদ্দেশ্য ছিল স্ট্যান্ডার্ড লাইব্রেরির কোডের জন্য নিয়ম — সারা বিশ্বের সব Python কোডের জন্য নয়। কিন্তু এত স্পষ্ট আর যুক্তিপূর্ণ ছিল যে পুরো সম্প্রদায় এটিকে নিজের করে নেয়।

PEP 8-এর শুরুর দিকেই একটি অংশ আছে যার শিরোনাম “A Foolish Consistency is the Hobgoblin of Little Minds”. বাক্যটি মার্কিন দার্শনিক Ralph Waldo Emerson-এর ১৮৪১ সালের প্রবন্ধ Self-Reliance থেকে। Emerson-এর প্রসঙ্গ ছিল চিন্তার স্বাধীনতা: গতকাল যা বলেছেন, শুধু ধারাবাহিক থাকার জন্য আজও তা আঁকড়ে থাকা নির্বোধ ধারাবাহিকতা — “ছোট মনের ভূত”.

একটি নিয়মের বই কেন এটি দিয়ে শুরু হয়? কারণ PEP 8-এর লেখকরা জানতেন স্টাইল গাইডের সবচেয়ে বড় বিপদ কী: মানুষ নিয়মকে লক্ষ্য বানিয়ে ফেলে। কোড review-তে কেউ লেখেন “PEP 8 বলেছে ৭৯ অক্ষর” — অথচ ৮২ অক্ষরের লাইনটি ভাঙলে একটি সুন্দর সূত্র দুই টুকরো হয়ে দুর্বোধ্য হতো। গাইডটি নিজেই বলে: স্টাইল গাইড ধারাবাহিকতার বিষয়; এই গাইডের সাথে ধারাবাহিকতা জরুরি, প্রকল্পের ভেতরে ধারাবাহিকতা আরও জরুরি, আর একটি module বা ফাংশনের ভেতরে ধারাবাহিকতা সবচেয়ে জরুরি। আর সবচেয়ে বড় কথা — কখন অসংগত হতে হবে, তা জানা চাই; সন্দেহ হলে নিজের বিচার-বুদ্ধি ব্যবহার করুন।

এর একটি বাস্তব উদাহরণ PEP 8-এর ইতিহাসেই আছে। দীর্ঘদিন PEP 8 বলত, লম্বা গাণিতিক রাশি ভাঙলে অপারেটর থাকবে লাইনের শেষে. ২০১৬ সালে সেটি বদলে সুপারিশ হয় অপারেটর লাইনের শুরুতে রাখা — কারণ গণিতের বই আর টাইপসেটিংয়ের প্রথা (Donald Knuth-এর উদ্ধৃতিসহ) দেখায় এভাবে পড়া সহজ; কোন সংখ্যা যোগ আর কোনটি বিয়োগ, বাঁ দিকে চোখ বুলিয়েই বোঝা যায়। অর্থাৎ PEP 8 নিজেও নিজের নিয়মকে চিরন্তন ভাবে না; পাঠযোগ্যতার প্রমাণ পেলে বদলায়। অংশ ২-এর লম্বা লাইনের উদাহরণে আমরা এই নতুন নিয়মটিই মেনেছি।

মূল উপলব্ধি: PEP 8 একটি আইনের বই নয়, একটি সম্প্রদায়ের সম্মিলিত রুচি — আর তার প্রথম শিক্ষাই হলো নিয়মের উদ্দেশ্য (পাঠযোগ্যতা) নিয়মের অক্ষরের চেয়ে বড়। নিয়ম মানুন, কারণ জানুন, আর যখন নিয়ম তার নিজের উদ্দেশ্যকেই ব্যর্থ করে, তখন সচেতনভাবে ভাঙুন।

প্র ০২black নামের formatter প্রায় কোনো সেটিং দেয় না — তবু কেন এত জনপ্রিয় হলো? “স্টাইলের তর্ক থামানো” আসলে কোন সমস্যার সমাধান?

Formatter নতুন নয়। Python-এ black-এর আগেও ছিল autopep8 আর Google-এর YAPF. YAPF-এর প্রচুর সেটিং — কোথায় লাইন ভাঙবে, বন্ধনী কীভাবে সাজাবে, কত রকম ধাঁচ। শুনতে ভালো, কিন্তু বাস্তবে এর মানে ছিল: প্রতিটি দলকে আগে বসে ঠিক করতে হতো কোন সেটিং, আর সেই মিটিং নিজেই একটি স্টাইল-তর্ক।

২০১৮ সালের মার্চে Python core developer Łukasz Langa প্রকাশ করেন black, স্লোগান “The uncompromising code formatter”. নামটি এসেছে Henry Ford-এর বিখ্যাত (সম্ভবত কিংবদন্তি) উক্তি থেকে — Model T গাড়ি “যেকোনো রঙে পাবেন, যতক্ষণ সেটি কালো”. black-এর নকশার মূল ভাবনা: সেটিং প্রায় নেই। লাইনের দৈর্ঘ্য বদলানো যায় (default ৮৮), আরও দু-একটি ছোট বিকল্প — ব্যস। আপনার কোড black-এর ভেতর দিয়ে গেলে একটিই রূপে বেরোবে, আপনি পছন্দ করুন বা না করুন। black আরও প্রতিশ্রুতি দেয়: ফরম্যাটের আগে-পরে কোডের AST মিলিয়ে দেখে, যাতে অর্থ এক বিন্দুও না বদলায়।

এই “আপসহীনতা” কেন জনপ্রিয় হলো? কারণ black আসলে যে সমস্যার সমাধান করে, তা কারিগরি নয়, মানবিক। স্টাইলের তর্কের একটি বিখ্যাত নাম আছে — bikeshedding: পারমাণবিক বিদ্যুৎকেন্দ্রের নকশা অনুমোদনের মিটিংয়ে সবাই কয়েক মিনিটে চুল্লি পাস করে দেন (কেউ বোঝেন না), কিন্তু সাইকেল রাখার ছাউনির রঙ নিয়ে এক ঘণ্টা তর্ক চলে (সবাই বোঝেন)। কোড review-তেও একই ঘটনা: একক না দ্বৈত কোট, বন্ধনীর আগে লাইন ভাঙবে কি না — এসব নিয়ে মন্তব্য করা সহজ, তাই সবচেয়ে বেশি মন্তব্য এগুলোতেই, আর আসল যুক্তির ভুল পার পেয়ে যায়। black সিদ্ধান্তটি মানুষের হাত থেকে সরিয়ে নিয়েছে। কারও ব্যক্তিগত রুচি জেতেনি — সবাই সমানভাবে “হেরেছে”, তাই কারও রাগ নেই।

আরেকটি লাভ diff-এ। সবার কোড একই নিয়মে ফরম্যাট হলে git diff-এ শুধু আসল বদল দেখা যায়, কারও editor-এর space-বদল নয়। black “magic trailing comma”-র মতো কৌশলও আনে: একটি list-এর শেষ উপাদানের পরে কমা রাখলে black প্রতিটি উপাদান আলাদা লাইনে রাখে — ফলে পরে একটি উপাদান যোগ করলে diff-এ একটিমাত্র লাইন বদলায়।

black দ্রুত বড় প্রকল্পগুলোতে ছড়িয়ে পড়ে, পরে Python Software Foundation-এর GitHub সংগঠনের অধীনে আসে, আর ২০২২ সালে প্রথম “stable” version প্রকাশিত হয়। আজ ruff format-এর মতো নতুন formatter-ও black-এর ধাঁচই অনুসরণ করে — অর্থাৎ black-এর আসল অবদান একটি টুল নয়, একটি মানদণ্ড।

মূল উপলব্ধি: কিছু সিদ্ধান্তে “সঠিক” উত্তরের চেয়ে “একটি” উত্তর বেশি মূল্যবান। black রুচির প্রশ্নকে যন্ত্রের হাতে দিয়ে মানুষের মনোযোগ ফিরিয়ে দিয়েছে সেখানে, যেখানে মানুষকেই লাগে — যুক্তি, নকশা আর ভুল খোঁজা।

প্র ০৩ruff কীভাবে কয়েক বছরের মধ্যে flake8, isort আর black-এর জায়গা নিতে শুরু করল? Python-এর টুল কেন ক্রমশ Rust-এ লেখা হচ্ছে, আর এর ঝুঁকি কী?

২০২২ সালের আগস্টে Charlie Marsh একটি ছোট পরীক্ষা হিসেবে ruff প্রকাশ করেন: flake8-এর নিয়মগুলো Rust-এ নতুন করে লিখলে কত দ্রুত হয়? উত্তর চমকে দেওয়ার মতো — তাঁর benchmark-এ বড় কোডবেসে flake8 যেখানে অনেক সেকেন্ড বা মিনিট নিত, ruff সেখানে সেকেন্ডের ভগ্নাংশ। এরপর দ্রুত একটার পর একটা টুলের নিয়ম যোগ হতে থাকে — isort, pyupgrade, flake8-bugbear, pydocstyle, আরও অনেক plugin. ২০২৩ সালে Marsh তাঁর কোম্পানি Astral-এর ঘোষণা দেন, বিনিয়োগ পান, পূর্ণকালীন দল গড়েন। একই বছর ruff-এ যোগ হয় formatter, যার লক্ষ্য black-এর সাথে প্রায় হুবহু মিল — যাতে বড় প্রকল্প প্রায় কোনো diff ছাড়াই সরে আসতে পারে। ২০২৪ সালে আসে uv — pip, venv আর আরও কয়েকটি টুলের দ্রুত বিকল্প (পর্ব ২৩)।

গতি এত বড় ব্যাপার কেন? কারণ গতি অভ্যাস বদলায়। যে টুল চালাতে এক মিনিট লাগে, সেটি মানুষ দিনে একবার চালায় — হয়তো CI-তে, যখন ভুল ইতিমধ্যে push হয়ে গেছে। যে টুল চোখের পলকে চলে, সেটি প্রতিবার ফাইল সেভ করার সময় চলে, আর ভুল ধরা পড়ে লেখার মুহূর্তেই। আর একটি টুলে দশটির কাজ মানে একটি config, একটি version, একটি সমন্বয় — পুরোনো ব্যবস্থায় flake8-এর plugin আর black-এর মধ্যে নিয়মের সংঘর্ষ ছিল নিয়মিত ঝামেলা।

কেন Rust? Python-এ লেখা টুল নিজেই Python interpreter-এর গতিতে সীমাবদ্ধ (পর্ব ৪৩-এর শিক্ষা — লুপটা C-তে ঠেলুন)। Rust সংকলিত ভাষা, C-এর কাছাকাছি গতি, কিন্তু মেমরি-নিরাপত্তার নিশ্চয়তাসহ, আর একাধিক core-এ নিরাপদে কাজ ভাগ করা সহজ। ruff-এর আগেও Python জগতে Rust ঢুকছিল — যেমন pydantic-এর মূল অংশ (pydantic-core) Rust-এ নতুন করে লেখা হয়েছে, আর PyO3 নামের লাইব্রেরি Rust থেকে Python extension বানানো সহজ করেছে। এটি পর্ব ৪৩-এর “Python is the glue” ভাবনার নতুন অধ্যায়: ভারী কাজ সংকলিত ভাষায়, ব্যবহারকারীর অভিজ্ঞতা Python-এ।

ঝুঁকি কী? প্রথমত, অবদান রাখার বাধা: একজন Python প্রোগ্রামার flake8-এর একটি plugin সহজেই লিখতে পারতেন; ruff-এ নতুন নিয়ম যোগ করতে Rust জানতে হয়, তাই ruff-এ আলাদা plugin ব্যবস্থাও নেই। দ্বিতীয়ত, কেন্দ্রীকরণ: Python-এর মূল টুলের একটি বড় অংশ এখন একটি venture-অর্থায়িত কোম্পানির হাতে। কোম্পানির ব্যবসায়িক সিদ্ধান্ত বদলালে কী হবে, এই প্রশ্ন সম্প্রদায়ে বারবার উঠেছে। ruff আর uv MIT/Apache-এর মতো খোলা লাইসেন্সে প্রকাশিত, তাই প্রয়োজনে কেউ fork করতে পারবে — এটি একটি সুরক্ষা, তবে পুরো দল ছাড়া রক্ষণাবেক্ষণ সহজ নয়। তৃতীয়ত, পুরোনো টুলগুলোর রক্ষণাবেক্ষণকারীদের ওপর চাপ — যাঁরা বছরের পর বছর স্বেচ্ছায় কাজ করেছেন।

মূল উপলব্ধি: ruff দেখায় যে ডেভেলপার-টুলে গতি শুধু সুবিধা নয়, আচরণ বদলের হাতিয়ার — দ্রুত টুল ভুলকে লেখার মুহূর্তে ধরে। সাথে প্রশ্নও তোলে: যে সম্প্রদায় স্বেচ্ছাসেবীর ঐতিহ্যে বড় হয়েছে, তার মূল টুল কার হাতে থাকবে আর কোন ভাষায় লেখা হবে?

প্র ০৪Zen of Python বলে “একটি — আর সম্ভব হলে একটিমাত্র — স্পষ্ট পথ থাকা উচিত”; Perl-এর মন্ত্র ছিল “There's more than one way to do it”. এই দুই দর্শনের পার্থক্য কী, আর বাস্তবে Python কি সত্যিই একটি পথ রাখতে পেরেছে?

১৯৯০-এর দশকে স্ক্রিপ্টিং জগতে Python-এর বড় প্রতিদ্বন্দ্বী ছিল Perl — Larry Wall-এর তৈরি (১৯৮৭), টেক্সট প্রক্রিয়া আর সিস্টেম-প্রশাসনে তখন প্রায় অপ্রতিদ্বন্দ্বী। Larry Wall ভাষাবিজ্ঞানে প্রশিক্ষিত ছিলেন, আর Perl-কে মানুষের ভাষার মতো নমনীয় করে বানিয়েছিলেন। তার মন্ত্র: TMTOWTDI — “There's more than one way to do it”, উচ্চারণ “টিম টোডি”. একই কাজ পাঁচভাবে লেখা যায়, আর প্রোগ্রামার নিজের মেজাজ আর প্রসঙ্গ অনুযায়ী বেছে নেবেন — যেমন কবি শব্দ বাছেন।

Tim Peters-এর Zen-এর ১৩ নম্বর বাক্যটি এর সরাসরি উত্তর: “There should be one-- and preferably only one --obvious way to do it.” খেয়াল করুন বাক্যের ড্যাশগুলো — একবার শব্দের সাথে লেগে, একবার ফাঁক রেখে। এটি ইচ্ছাকৃত রসিকতা বলে ব্যাপকভাবে ধরা হয়: ইংরেজিতে em-dash লেখার কোনো “একটি স্পষ্ট পথ” নেই, আর বাক্যটি নিজেই সেটি দেখায়। পরের বাক্যটিও রসিকতা: “যদিও সেই পথ শুরুতে স্পষ্ট নাও হতে পারে, যদি না আপনি ডাচ হন” — Guido নেদারল্যান্ডসের মানুষ।

দুই দর্শনের আসল পার্থক্য লেখক আর পাঠকের মধ্যে কার সুবিধা আগে। TMTOWTDI লেখকের স্বাধীনতা বাড়ায় — নিজের মতো প্রকাশ। কিন্তু পাঠককে সব পথ চিনতে হয়; আর Perl-এর বিশেষ চিহ্নভরা কোড “write-only language”-এর দুর্নাম পেয়েছিল — লেখা যায়, পরে পড়া যায় না। Python-এর দর্শন পাঠককে আগে রাখে: একটি প্রচলিত পথ থাকলে প্রতিটি পাঠক সেটি চেনেন, আর অপরিচিত ধাঁচ দেখলেই সতর্ক হন — “এখানে নিশ্চয়ই বিশেষ কিছু হচ্ছে।” এই পর্বের পুরো idiom-এর তালিকা এই দর্শনেরই ফল।

তবে সত্যি কথা বলতে, Python নিখুঁতভাবে “একটি পথ” রাখতে পারেনি। string ফরম্যাটের কথাই ধরুন: %-ফরম্যাটিং, str.format(), string.Template, f-string, আর Python 3.14-এর template string (t-string) — অন্তত পাঁচটি পথ! ফাইলের পথের জন্য os.path আর pathlib. package বানানোর টুল তো অগণিত। কারণ বাস্তবতা: ভাষা বিকশিত হয়, নতুন ও ভালো পথ আসে, কিন্তু লাখো পুরোনো কোড ভাঙা যায় না বলে পুরোনো পথ সরানো যায় না। তাই Zen-এর শব্দচয়ন লক্ষ করুন — “obvious way”. সব সময় একটিমাত্র পথ থাকবে, তা নয়; বরং একটি পথ থাকবে যা স্পষ্টভাবে প্রস্তাবিত — আজকের দিনে সাধারণ string-এ f-string, পথে pathlib. পুরোনো পথ চেনা দরকার, কারণ পুরোনো কোড পড়বেন; নতুন কোডে প্রচলিত পথটি লিখুন।

মূল উপলব্ধি: “একটি স্পষ্ট পথ” কোনো প্রযুক্তিগত নিষেধ নয়, একটি সাংস্কৃতিক প্রতিশ্রুতি — সম্প্রদায় একমত হয় কোন পথটি প্রস্তাবিত, যাতে পাঠক চিনতে পারেন। Pythonic হওয়া মানে সেই সম্মিলিত সিদ্ধান্তে অংশ নেওয়া।

প্র ০৫“কোড যতবার লেখা হয়, তার চেয়ে অনেক বেশিবার পড়া হয়” — Guido-র এই উপলব্ধির পক্ষে কী প্রমাণ আছে? পাঠযোগ্যতা কি মাপা যায়, আর এর অর্থনৈতিক মূল্য কী?

PEP 8-এর শুরুর দিকের একটি অংশেই আছে: Guido-র অন্যতম প্রধান উপলব্ধি হলো কোড লেখার চেয়ে অনেক বেশিবার পড়া হয়, আর এই গাইডের উদ্দেশ্য পাঠযোগ্যতা বাড়ানো ও কোডকে সামঞ্জস্যপূর্ণ করা। Python-এর পুরো নকশাতেই এর ছাপ — indentation বাধ্যতামূলক (যাতে চেহারা আর গঠন কখনো আলাদা না হয়), &&-এর বদলে and, ইংরেজির মতো পড়া যায় এমন if x in items.

প্রমাণ কী? সফটওয়্যার প্রকৌশলের অভিজ্ঞতা বলে, একটি সফল প্রোগ্রামের জীবনের বেশিরভাগ খরচ যায় প্রথম সংস্করণের পরে — রক্ষণাবেক্ষণে: বাগ ঠিক করা, নতুন চাহিদা যোগ, পরিবেশ বদলের সাথে মানিয়ে নেওয়া। আর রক্ষণাবেক্ষণের প্রতিটি কাজ শুরু হয় পড়া দিয়ে — কোথায় বদলাতে হবে, বদলালে কী ভাঙবে। Robert C. Martin তাঁর Clean Code (২০০৮) বইয়ে লেখেন, কোড পড়া আর লেখার সময়ের অনুপাত দশ-এক-এরও অনেক বেশি; আমরা নতুন কোড লেখার প্রক্রিয়ার অংশ হিসেবেও অবিরাম পুরোনো কোড পড়ি। এটি তাঁর অভিজ্ঞতাভিত্তিক অনুমান, কড়া মাপ নয় — কিন্তু যেকোনো পেশাদার প্রোগ্রামার নিজের দিনের হিসাব নিলে দেখবেন, নতুন লাইন টাইপ করার চেয়ে কোড পড়া, খোঁজা আর বোঝায় অনেক বেশি সময় যায়।

পাঠযোগ্যতা কি মাপা যায়? গবেষকরা চেষ্টা করেছেন। ২০০৮ সালের দিকে Raymond Buse ও Westley Weimer একটি পরিচিত গবেষণায় অনেক মানুষকে কোডের ছোট টুকরো দেখিয়ে পাঠযোগ্যতার নম্বর দিতে বলেন, তারপর সেই নম্বরের সাথে মিলে এমন একটি স্বয়ংক্রিয় মডেল বানান। তাঁদের ফলাফলে সাধারণ কিছু বৈশিষ্ট্য — যেমন লাইনের দৈর্ঘ্য আর এক লাইনে কতগুলো নাম (identifier) — পাঠযোগ্যতার সাথে জোরালোভাবে সম্পর্কিত দেখা যায়। পরের গবেষণাগুলো দেখিয়েছে, শুধু চেহারা নয়, ভালো নাম আর চেনা ধাঁচও বোঝার গতি বাড়ায়; আবার এটাও দেখা গেছে যে কোনো একটি সংখ্যা দিয়ে পাঠযোগ্যতা পুরোপুরি ধরা কঠিন, কারণ এটি পাঠকের অভিজ্ঞতার ওপরও নির্ভর করে। এই শেষ কথাটিই “idiom”-এর যুক্তি: Python প্রোগ্রামাররা যে ধাঁচ হাজারবার দেখেছেন, সেটি তাঁদের কাছে সবচেয়ে পাঠযোগ্য — কারণ চেনা।

অর্থনৈতিক দিকটা সহজ হিসাব। ধরুন একটি দলে দশজন প্রোগ্রামার, প্রত্যেকে দিনে এক ঘণ্টা অগোছালো কোড বুঝতে নষ্ট করেন। বছরে সেটি দুই হাজারের বেশি কর্মঘণ্টা — একজন পূর্ণকালীন কর্মীর প্রায় পুরো বছর। আর পাঠযোগ্যতার অভাবের সবচেয়ে বড় খরচ সময় নয়, ভুল: যে কোড ভুল বোঝা হয়, তাতে বদল আনলে নতুন বাগ ঢোকে। লেখার সময় অতিরিক্ত দুই মিনিট — একটি ভালো নাম, একটি guard clause, একটি “কেন” comment — এই খরচের তুলনায় নগণ্য। AI-এর যুগে এই হিসাব আরও তীব্র: কোড লেখার খরচ নামছে, তাই পড়া আর যাচাই করাই এখন প্রোগ্রামারের সময়ের বড় অংশ।

মূল উপলব্ধি: পাঠযোগ্যতা কোনো সৌন্দর্যচর্চা নয়, একটি অর্থনৈতিক সিদ্ধান্ত — কোডের জীবনের বেশিরভাগ খরচ পড়ায়। আর এটি পাঠকের পরিচিতির ওপর নির্ভর করে বলেই সম্প্রদায়ের প্রচলিত ধাঁচ (idiom, PEP 8) ব্যক্তিগত রুচির চেয়ে মূল্যবান।

অনুশীলন

বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — পড়া, চেনা আর সিদ্ধান্ত নেওয়াই আসল কাজ।

  1. নাম ঠিক করুন: একটি ফাইলে এই নামগুলো আছে — StudentCount (একটি int), calculatetotal(), class bank_account, vat (একটি ধ্রুবক ০.১৫), l (একটি list), str_phone, getName(). প্রতিটির PEP 8-সম্মত আর অর্থপূর্ণ নাম প্রস্তাব করুন।

    StudentCount → student_count (ভ্যারিয়েবল, তাই snake_case; PascalCase দেখে কেউ class ভাববেন)। calculatetotal() → calculate_total() (শব্দের মাঝে _)। class bank_account → class BankAccount. vat → VAT_RATE = 0.15 (ধ্রুবক, আর “rate” বলে দেয় এটি অনুপাত, টাকা নয়)। l → অর্থবহ বহুবচন, যেমন marks বা orders — একক l অনেক ফন্টে 1-এর মতো দেখায় (ruff-এর E741). str_phone → phone বা phone_number (Hungarian notation বাদ; type জানাতে hint: phone: str). getName() → যদি শুধু একটি attribute ফেরত দেয়, তাহলে method-ই বাদ দিয়ে সরাসরি obj.name; হিসাব লাগলে @property — আর ফাংশন হিসেবে রাখতে হলে get_name().

  2. ruff-এর ভূমিকায় আপনি: নিচের কোডে কোন কোন সমস্যা একটি linter ধরবে, কোনগুলো formatter ঠিক করবে, আর কোনগুলো কোনো টুলই ধরবে না? import re (কোথাও ব্যবহার নেই); def avg(x):;     if x == []: return 0;     s=0;     for i in range(len(x)): s=s+x[i];     return s/len(x).

    Linter ধরবে: অব্যবহৃত import re (F401, --fix নিজেই মুছবে); এক লাইনে if ...: return (E701, বহু statement এক লাইনে) — নিয়ম-তালিকায় E থাকলে। Formatter ঠিক করবে: s=0 → s = 0, s=s+x[i] → s = s + x[i], এক লাইনের if আর for-এর দেহ নিচের লাইনে নামানো। কোনো টুলই সাধারণত ধরবে না (মানুষের কাজ): x == []-এর বদলে if not x:; range(len(x))-এর বদলে সরাসরি sum(x); অর্থহীন নাম avg, x, s → average(marks); আসলে পুরো ফাংশনটিই হতে পারত statistics.mean বা sum(marks) / len(marks) if marks else 0. (pylint বা ruff-এর কিছু অতিরিক্ত নিয়ম চালু করলে এর কয়েকটি ধরা পড়তে পারে, কিন্তু নাম আর নকশার বিচার শেষ পর্যন্ত মানুষেরই।)

  3. নিয়ম ভাঙবেন কি? তিনটি পরিস্থিতি: (ক) একটি পুরোনো প্রকল্পের সব ফাংশন camelCase-এ, আপনি একটি নতুন ফাংশন যোগ করছেন। (খ) একটি ম্যাট্রিক্সের মান হাতে লিখছেন, সারি-কলাম মিলিয়ে সাজাতে space দিয়ে সারিবদ্ধ করতে চান, কিন্তু formatter সেটি ভেঙে দেয়। (গ) একটি URL-সহ comment ১১০ অক্ষরের। প্রতিটিতে কী করবেন?

    (ক) প্রকল্পের ধাঁচ মানুন — camelCase. PEP 8 নিজেই বলে প্রকল্পের ভেতরের ধারাবাহিকতা গাইডের চেয়ে জরুরি; একটি ফাইলে দুই ধাঁচ সবচেয়ে খারাপ। পুরো প্রকল্প snake_case-এ আনতে চাইলে সেটি আলাদা, পরিকল্পিত একটি refactor হোক (দলের সম্মতিতে, একটি আলাদা pull request-এ)। (খ) এটিই “foolish consistency” এড়ানোর জায়গা: ruff format আর black দুটিই # fmt: off ও # fmt: on comment দিয়ে একটি অংশ ফরম্যাটের বাইরে রাখতে দেয়। শুধু সেই ছোট অংশে ব্যবহার করুন, কারণসহ। (গ) URL ভাঙলে কাজ করে না, তাই লাইনটি লম্বাই রাখুন; ruff-এ E501 (লাইন বেশি লম্বা) নিয়ম চালু থাকলে সেই লাইনে # noqa: E501. অনেক টুল URL-ওয়ালা লম্বা লাইনকে নিজেই ছাড় দেয়। মূল শিক্ষা: নিয়ম ভাঙুন সচেতনভাবে, অল্প জায়গায়, আর এমনভাবে যাতে পাঠক বোঝেন এটি ইচ্ছাকৃত।

  4. Pythonic করুন (কোড লিখতে হবে): নিচের ফাংশনটিকে আচরণ না বদলে Pythonic করুন, আর একটি ছোট test দিয়ে প্রমাণ করুন যে ফল একই: def countPassed(m): / c = 0 / for i in range(len(m)): / if m[i] >= 33 and m[i] != None: / c = c + 1 / return c — যেখানে m-এ None থাকতে পারে (অনুপস্থিত শিক্ষার্থী)।

    একটি সমাধান:

    PASS_MARK = 33
    
    
    def count_passed(marks: list[int | None]) -> int:
        """Return how many students reached the pass mark (None = absent)."""
        return sum(1 for mark in marks if mark is not None and mark >= PASS_MARK)
    
    
    def test_same_as_old():
        data = [40, None, 32, 33, 90, None]
        assert count_passed(data) == 3
        assert count_passed([]) == 0

    একটি সূক্ষ্ম ফাঁদ লক্ষ করুন: মূল কোডে শর্তের ক্রম ছিল m[i] >= 33 and m[i] != None — None এলে প্রথম তুলনাতেই Python 3-এ TypeError! অর্থাৎ মূল কোডটি আসলে None-ওয়ালা ডেটায় ভেঙে পড়ত। Pythonic রূপে আগে is not None, তারপর তুলনা — and-এর short-circuit দ্বিতীয় অংশে যেতেই দেয় না। এটি দেখায় কেন refactor-এর আগে test লিখতে হয়: test লিখতে গিয়েই ধরা পড়ে, “আগের আচরণ” আসলে একটি বাগ ছিল। তখন সিদ্ধান্ত নিন — বাগটি ঠিক করবেন (এখানে স্পষ্টতই হ্যাঁ), কিন্তু সেটি জেনে-বুঝে, আলাদা করে উল্লেখ করে। sum(1 for ...)-এর বদলে len([...])-ও চলে; গোনা বোঝাতে sum প্রচলিত।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমPEP 8 অনুযায়ী একটি Python ফাইলের শুরুর অংশ ওপর থেকে নিচে সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. module-এর docstring
  2. standard library-র import (যেমন import json)
  3. third-party package-এর import (যেমন import requests)
  4. নিজের প্রকল্পের import (যেমন from shop.models import Product)
  5. module-স্তরের ধ্রুবক (যেমন PASS_MARK = 40)

PEP 8 অনুযায়ী Python-এ একটি class, একটি ফাংশন আর একটি module-স্তরের ধ্রুবকের নাম কোন ধাঁচে লেখা উচিত?

  • studentRecord, calcGpa, passMark
  • student_record, CalcGpa, PASS_MARK
  • StudentRecord, calc_gpa, PASS_MARK
  • StudentRecord, calc_gpa, pass_mark

Python-এ কোনো ভ্যারিয়েবল None কি না যাচাইয়ের সবচেয়ে উপযুক্ত (Pythonic) উপায় কোনটি?

  • if result == None:
  • if result is None:
  • if not result:
  • if type(result) == type(None):

Python-এ names আর marks নামের দুটি সমান দৈর্ঘ্যের list পাশাপাশি পড়ে প্রতিটি নাম ও নম্বর ছাপাতে সবচেয়ে Pythonic লুপ কোনটি?

  • for i in range(len(names)): print(names[i], marks[i])
  • for i, name in enumerate(names): print(name, marks[i])
  • i = 0, তারপর while i < len(names): লুপে ছাপিয়ে i += 1
  • for name, mark in zip(names, marks, strict=True): print(name, mark)

Python-এর টুলের প্রসঙ্গে linter আর formatter-এর মূল পার্থক্য কোনটি?

  • linter সম্ভাব্য ভুল ও খারাপ অভ্যাস জানায়; formatter অর্থ না বদলে কোডের চেহারা ঠিক করে
  • linter কোড চালিয়ে দেখে, formatter চালায় না
  • formatter বাগ ঠিক করে, linter শুধু space ঠিক করে
  • দুটি একই কাজের দুটি নাম

Python-এ def add_item(item, cart=[]): ফাংশনটি cart.append(item) করে cart ফেরত দেয়। দ্বিতীয় argument ছাড়া পরপর দুবার ডাকলে কী ঘটে, আর Pythonic সমাধান কী?

  • প্রতিবার নতুন খালি list; কোনো সমস্যা নেই
  • SyntaxError আসে; default-এ list দেওয়া যায় না
  • দুই ডাক একই list ভাগ করে, দ্বিতীয়বারে আগের পণ্যও থাকে; cart=None দিয়ে ভেতরে নতুন list বানানো
  • Python default list-কে tuple বানিয়ে দেয়, তাই append-এ error

একটি Python প্রকল্পে প্রতিটি git commit-এর আগে স্বয়ংক্রিয়ভাবে ruff চালিয়ে সমস্যা থাকলে commit আটকাতে সাধারণত কোন ব্যবস্থা ব্যবহার করা হয়?

  • mypy-র strict mode
  • .pre-commit-config.yaml আর pre-commit install
  • প্রকল্পের __init__.py-তে import ruff লেখা
  • requirements.txt-এ ruff যোগ করা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. কোড পড়া হয় লেখার চেয়ে অনেক বেশি। Pythonic মানে প্রচলিত ধাঁচ, যা যেকোনো Python প্রোগ্রামার এক নজরে চেনেন; চালাক নয়, স্পষ্ট। মাপকাঠি: কত দ্রুত আর নিশ্চিতভাবে বোঝা যায়।
  2. PEP 8: snake_case ফাংশন/ভ্যারিয়েবল, PascalCase class, UPPER_SNAKE_CASE ধ্রুবক, _ দিয়ে ভেতরের নাম; import তিন দলে (standard, third-party, local); লাইনের সীমা প্রকল্পে একবার ঠিক করুন। পাঠযোগ্যতা কমলে সচেতনভাবে নিয়ম ভাঙুন।
  3. প্রতিদিনের idiom: if items:, is None, in, chained comparison, enumerate/zip, unpacking, comprehension, with, EAFP, dict.get/defaultdict, f-string, any/all, sorted(key=), pathlib, None default, early return.
  4. নাম, docstring, comment: ফাংশন ক্রিয়া, ডেটা বিশেষ্য, টাইপ নামে নয়; PEP 257 docstring; comment “কেন” বলে; public API-তে type hint. Refactor করুন এক ধাপে এক বদল, test-সহ।
  5. টুল স্টাইল সামলাক: ruff (linter + formatter), black, isort, mypy; নিয়ম pyproject.toml-এ, pre-commit আর CI-তে চালান। Review-তে যুক্তি দেখুন, মানুষ নয় কোড নিয়ে বলুন। AI-এর কোডেও ruff আর চেকলিস্ট।

পরের পর্বে: পরিষ্কার কোড লেখার পরে এবার দক্ষ কোড ভাবার পালা — অ্যালগরিদম ও কনটেস্টে Python: সাজানো, খোঁজা, গ্রাফ আর dynamic programming-এ Python-এর নিজস্ব হাতিয়ার, আর কনটেস্টে Python ব্যবহারের কৌশল ও সীমা।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান