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

লাইব্রেরি ভ্রমণ — datetime, random, collections, os আর আরও

A tour of the standard library — datetime, random, collections, os and more

Taught in Bangla, this lesson tours Python's batteries-included standard library: datetime and naive versus aware times with Asia/Dhaka, random versus secrets, collections, heapq and bisect, os, sys, shutil and statistics, and small gems such as sqlite3, hashlib and uuid.

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

  • “batteries included” দর্শন — standard library-র পরিবারগুলো, আর কোনটি এই কোর্সের কোন পর্বে বিস্তারিত
  • datetime দিয়ে তারিখের যোগ-বিয়োগ, dd/mm/yyyy পড়া-লেখা, naive বনাম aware সময় আর Asia/Dhaka time zone
  • seed করা random দিয়ে পুনরাবৃত্তিযোগ্য টেস্ট, আর নিরাপত্তার কাজে কেন শুধু secrets
  • Counter, defaultdict, deque, heapq, bisect — কোন সমস্যায় কোন যন্ত্র
  • os, sys, shutil, statistics, sqlite3, hashlib, uuid — আর নতুন module খোঁজা ও third-party কখন নেবেন

১ · নিজে লেখার আগে বাক্সটা খুলুন — “batteries included”

পর্ব ২২-এ দেখেছেন, একটি module হলো আলাদা ফাইলে রাখা কোড, যা import দিয়ে আনা যায়। পর্ব ২৩-এ দেখেছেন, pip দিয়ে ইন্টারনেট থেকে অন্যের লেখা package নামানো যায়। কিন্তু এই দুইয়ের মাঝে একটি বিশাল ভান্ডার আছে, যা আপনার কম্পিউটারে ইতোমধ্যেই আছে — Python ইনস্টল করার দিন থেকে। এর নাম standard library.

একটি উদাহরণ দিয়ে শুরু করি। ধরুন একটি কোচিং সেন্টারের জন্য প্রোগ্রাম লিখছেন। দরকার: (১) ভর্তি পরীক্ষার আর কত দিন বাকি, (২) লটারিতে বৃত্তির জন্য তিনজন বাছাই, (৩) কোন বিষয়ে সবচেয়ে বেশি ভর্তি, (৪) শিক্ষার্থীদের তালিকা কোথাও গুছিয়ে রাখা। চারটি কাজই নিজে লেখা যায় — মাসে কত দিন, লিপ ইয়ার কোনটি, গণনার dict, একটি নিজস্ব ফাইল-ফরম্যাট। কিন্তু প্রতিটির জন্য Python-এর সাথে একটি তৈরি, পরীক্ষিত module আছে: datetime, random, collections.Counter, sqlite3. একটি import লাইন — আর কয়েক হাজার মানুষের বছরের পর বছর পরীক্ষা করা কোড আপনার হাতে।

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

Python সম্প্রদায় এই দর্শনকে বলে batteries included — খেলনা কিনলে ব্যাটারি আলাদা কিনতে হয় না, বাক্সেই থাকে। Python-এর অফিসিয়াল টিউটোরিয়ালের standard library অধ্যায়েও “Batteries Included” নামে একটি অংশ আছে। নিজে লেখার বদলে standard library ব্যবহারের তিনটি বড় লাভ:

  • সঠিকতা: তারিখের হিসাবে লিপ ইয়ার, মাসের ভিন্ন দৈর্ঘ্য, time zone-এর বদল — এমন কোণার ঘটনা (edge case) নিজের কোডে বাদ পড়ে যায়। standard library-র কোড কোটি কোটি প্রোগ্রামে চলেছে; বেশিরভাগ বাগ বহু আগেই ধরা পড়ে ঠিক হয়েছে।
  • সবখানে পাওয়া যায়: যে কম্পিউটারে Python আছে, সেখানেই এগুলো আছে। কোনো pip install লাগে না, কোনো version-এর গোলমাল নেই, কোনো অচেনা লেখকের কোডে ভরসা করতে হয় না।
  • পড়া সহজ: Counter(words).most_common(3) দেখলে যেকোনো Python প্রোগ্রামার বোঝেন কী হচ্ছে। নিজের লেখা ১৫ লাইনের লুপ বুঝতে সময় লাগে।
মূল ধারণা

standard library-র প্রতিটি ফাংশন মুখস্থ করার দরকার নেই — কেউই করেন না। দরকার একটি মানচিত্র: কোন ধরনের সমস্যার জন্য কোন module আছে। তাহলে সমস্যা এলে আপনি জানবেন “এর জন্য নিশ্চয়ই কিছু আছে, খুঁজে দেখি” — আর বিস্তারিত দেখে নেবেন ডকুমেন্টেশনে। এই পর্ব সেই মানচিত্র আঁকার জন্য।

২ · লাইব্রেরির মানচিত্র — কোন পরিবারে কী

standard library-র module-গুলোকে কয়েকটি পরিবারে সাজালে মনে রাখা সহজ। নিচের টেবিলে প্রতিটি পরিবার, প্রধান module আর এই কোর্সের কোন পর্বে সেটি বিস্তারিত শেখানো হয়েছে বা হবে। যেগুলো অন্য পর্বে আছে, সেগুলো এখানে শুধু নাম ধরে চিনিয়ে দেওয়া হবে।

পরিবারপ্রধান moduleকী দেয়কোর্সে
সংখ্যা ও গণিতmath, decimal, fractions, statisticsগণিতের ফাংশন, নির্ভুল দশমিক, গড়-মধ্যমাপর্ব ০৭, statistics এই পর্বে
সময় ও তারিখdatetime, zoneinfo, time, calendarতারিখের হিসাব, time zone, সময় মাপাএই পর্ব
এলোমেলো ও নিরাপত্তাrandom, secrets, hashlib, uuidলটারি, OTP, hash, অনন্য পরিচয়এই পর্ব, পর্ব ৫০
ডেটা স্ট্রাকচারcollections, heapq, bisectCounter, deque, heap, সাজানো ঢোকানোএই পর্ব (namedtuple: পর্ব ১৫)
ফাংশনের যন্ত্রitertools, functools, operatoriterator-এর কৌশল, cache, partialপর্ব ৩৮
লেখাre, string, textwrap, unicodedataপ্যাটার্ন খোঁজা, লেখা ভাঙা-সাজানোপর্ব ২৮, ছোট অংশ এখানে
ফাইল ও ডেটা ফরম্যাটpathlib, shutil, csv, json, sqlite3, zipfileফাইল, ফোল্ডার, CSV, JSON, databaseপর্ব ২৫, পর্ব ২৬, sqlite3 এখানে
অপারেটিং সিস্টেমos, sys, platform, subprocess, argparseenvironment, command-line, অন্য প্রোগ্রাম চালানোএই পর্ব, পর্ব ৪৯
একসাথে অনেক কাজthreading, multiprocessing, asynciothread, process, asyncপর্ব ৪১, ৪২
মান রক্ষাunittest, logging, pdb, timeitটেস্ট, লগ, ডিবাগার, গতি মাপাপর্ব ৩৯, ৪০
নেটওয়ার্কurllib, http, socket, emailওয়েব থেকে আনা, সার্ভার, ইমেইলপর্ব ৪৭

টেবিলটি সম্পূর্ণ নয় — এটি একটি ভ্রমণের নকশা মাত্র। আজ আমরা ঘুরব সেই কোণগুলোতে, যেগুলো আলাদা পর্ব পায়নি অথচ প্রায় প্রতিটি বাস্তব প্রোগ্রামে লাগে: সময়, এলোমেলো সংখ্যা, collections, অপারেটিং সিস্টেম আর একটি ছোট database. প্রতিটি অংশের লক্ষ্য একটাই — “এটা আছে, এই সমস্যায় লাগে, এখানে ফাঁদ” — এই তিনটি কথা মাথায় গেঁথে দেওয়া।

৩ · datetime — তারিখ, সময় আর তাদের পার্থক্য

তারিখের হিসাব শুনতে সহজ, কিন্তু নিজে লিখলে ভুলের খনি। জানুয়ারিতে ৩১ দিন, ফেব্রুয়ারিতে ২৮ বা ২৯, লিপ ইয়ারের নিয়মে ১০০ আর ৪০০-র ব্যতিক্রম… datetime module এগুলো সব জানে। এর মূল চারটি টাইপ:

📅 date

শুধু তারিখ — বছর, মাস, দিন। জন্মতারিখ, পরীক্ষার দিন, ছুটির দিন।

date(2026, 12, 16)

🕒 time

শুধু দিনের সময় — ঘণ্টা, মিনিট, সেকেন্ড। দোকান খোলার সময়, ক্লাসের সময়।

time(9, 30)

🗓️ datetime

তারিখ আর সময় একসাথে — একটি নির্দিষ্ট মুহূর্ত। লেনদেনের সময়, ফ্লাইট ছাড়ার সময়।

datetime(2026, 11, 5, 14, 30)

⏳ timedelta

দুটি মুহূর্তের দূরত্ব — কত দিন, কত ঘণ্টা। তারিখের সাথে যোগ-বিয়োগ করা যায়।

timedelta(days=30)

সবচেয়ে কাজের ধারণাটি হলো: দুটি তারিখ বিয়োগ করলে একটি timedelta পাওয়া যায়, আর তারিখের সাথে timedelta যোগ করলে নতুন তারিখ. ধরুন আজকের তারিখ (উদাহরণের জন্য স্থির) ২০২৬ সালের ১৭ অক্টোবর, আর বিজয় দিবস ১৬ ডিসেম্বর। date(2026, 12, 16) - date(2026, 10, 17) দেয় timedelta(days=60), তার .days হলো 60. কোন মাসে কত দিন, তা আপনাকে ভাবতেই হলো না।

এই কোর্সের চালানো যায় এমন কোডে কখনো date.today() বা datetime.now() ছাপা হয় না — কারণ আজ চালালে এক ফল, কাল আরেক। নিজের প্রোগ্রামে অবশ্যই “আজ” ব্যবহার করবেন; কিন্তু হিসাবের ফাংশনটি লিখুন এমনভাবে, যাতে “আজ” বাইরে থেকে argument হিসেবে আসে — def days_left(exam, today):. তাহলে টেস্টে একটি স্থির তারিখ দিয়ে ফল যাচাই করা যায়। এই অভ্যাস পর্ব ৩৯-এর টেস্টিংয়ে খুব কাজে লাগবে।

নিচের ধাপে ধাপে ছবিতে দেখুন, ৩০ দিন যোগ করলে কীভাবে মাসের সীমা পেরিয়ে যায় — ৩১ দিনের জানুয়ারি আর ২৮ দিনের ফেব্রুয়ারি, দুটোই datetime নিজে সামলায়:

1start = date(2026, 1, 20)2step = timedelta(days=30)3d1 = start + step4d2 = d1 + step5gap = date(2026, 12, 16) - start6print(gap.days, d2.strftime("%d/%m/%Y"))
নাম → মান (সবুজ লাইন এইমাত্র চলেছে)
start
step
d1
d2
gap
  1. একটি তারিখ আর একটি দূরত্ব। start একটি date object — ২০ জানুয়ারি ২০২৬. step একটি timedelta — “৩০ দিন”. খেয়াল করুন, timedelta-র কোনো শুরু বা শেষ নেই; এটি শুধু একটি দৈর্ঘ্য, ঠিক যেমন “৫ কিলোমিটার” কোনো রাস্তার নাম নয়।
  2. ৩০ দিন পর — ফেব্রুয়ারিতে। জানুয়ারিতে ৩১ দিন, তাই ২০ তারিখের পর জানুয়ারিতে আর ১১ দিন। বাকি ১৯ দিন ফেব্রুয়ারিতে — ফল ১৯ ফেব্রুয়ারি। নিজে লিখলে “দিন + ৩০, ৩০-এর বেশি হলে মাস + ১” — এই ভুল সূত্র অনেকেই লেখেন; তা এখানে ২০ ফেব্রুয়ারি দিত।
  3. আরও ৩০ দিন — মার্চে। ২০২৬ লিপ ইয়ার নয়, তাই ফেব্রুয়ারিতে ২৮ দিন। ১৯ ফেব্রুয়ারির পর ফেব্রুয়ারিতে ৯ দিন, মার্চে ২১ — ফল ২১ মার্চ। ২০২৮ সাল হলে (লিপ ইয়ার) উত্তর হতো ২০ মার্চ — datetime সেটাও জানে।
  4. দুটি তারিখের বিয়োগ। date - date দেয় একটি timedelta — এখানে ৩৩০ দিন। এভাবেই “পরীক্ষার আর কত দিন”, “বিল কত দিন বাকি”, “সদস্যপদ কবে শেষ” ধরনের প্রশ্নের উত্তর আসে। date + date কিন্তু অর্থহীন, তাই TypeError দেয়।
  5. ছাপা — বাংলাদেশি ধাঁচে। gap.days একটি সাধারণ int: 330. আর strftime("%d/%m/%Y") তারিখটিকে দিন/মাস/বছর রূপে লেখা বানায় — 21/03/2026. ভেতরে datetime সবসময় একই object রাখে; কীভাবে দেখাবেন, তা শুধু ছাপার সময়ের সিদ্ধান্ত।

লেখা থেকে তারিখ, তারিখ থেকে লেখা — strptime আর strftime

ফর্মে মানুষ তারিখ লেখে লেখা (string) হিসেবে, আর বাংলাদেশে সবচেয়ে প্রচলিত ধাঁচ দিন/মাস/বছর — 25/12/2026. এই লেখাকে datetime বানাতে strptime (string parse time), আর datetime থেকে লেখা বানাতে strftime (string format time). দুটিই একই সংকেত ব্যবহার করে:

সংকেতমানেউদাহরণ
%dমাসের দিন, দুই অঙ্কে05
%mমাস, দুই অঙ্কে11
%Y / %yচার অঙ্কের / দুই অঙ্কের বছর2026 / 26
%H / %I %p২৪ ঘণ্টার ঘড়ি / ১২ ঘণ্টা আর AM-PM14 / 02 PM
%M / %Sমিনিট / সেকেন্ড30 / 00
%A / %Bবারের নাম / মাসের নাম (ইংরেজি, locale-নির্ভর)Thursday / November

বড় হাতের %M মানে মিনিট, ছোট হাতের %m মানে মাস — এই দুটি গুলিয়ে ফেলা সবচেয়ে প্রচলিত ভুল, আর এতে কোনো error আসে না, শুধু ভুল তারিখ আসে। আরেকটি ফাঁদ: 05/11/2026 বাংলাদেশে ৫ নভেম্বর, কিন্তু যুক্তরাষ্ট্রের ধাঁচে (মাস/দিন) ১১ মে। তাই অন্য দেশের সিস্টেমের সাথে ডেটা আদান-প্রদানে কোনো আঞ্চলিক ধাঁচ নয়, আন্তর্জাতিক মান ISO 8601 ব্যবহার করুন — 2026-11-05. এটি পড়তে আছে date.fromisoformat("2026-11-05"), লিখতে .isoformat(). ISO ধাঁচের আরেকটি সুবিধা: সাধারণ string হিসেবে সাজালেও তারিখের ক্রমেই সাজে। (Python 3.11 থেকে fromisoformat ISO 8601-এর বেশিরভাগ রূপ পড়তে পারে; আগের version-এ সীমিত ছিল।)

সময় মাপা — time.perf_counter

কোনো কাজে কত সময় লাগছে, তা মাপতে ঘড়ির সময় (datetime.now() বা time.time()) নয়, ব্যবহার করুন time.perf_counter(). কারণ ঘড়ির সময় কম্পিউটার নিজে ঠিক করে নিতে পারে (ইন্টারনেট থেকে সময় মিলিয়ে) — তখন মাপের মাঝখানে ঘড়ি পিছিয়ে গেলে ঋণাত্মক সময়ও আসতে পারে। perf_counter শুধু সামনে চলে, আর সূক্ষ্মতম মাপ দেয়:

import time

start = time.perf_counter()
total = sum(range(10_000_000))
elapsed = time.perf_counter() - start
print(f"সময় লাগল {elapsed:.3f} সেকেন্ড")   # প্রতিবার, প্রতি মেশিনে আলাদা

এর একটি মানই অর্থহীন — শুধু দুটি মানের পার্থক্য কাজের। এক রানের ফল দেখে সিদ্ধান্ত নেবেন না; বারবার মাপার জন্য timeit module আছে, আর কীভাবে ঠিকমতো মাপতে হয়, তা পর্ব ৪৩-এ।

৪ · time zone — naive বনাম aware সময়, আর Asia/Dhaka

datetime(2026, 11, 5, 14, 30) — দুপুর আড়াইটা। কিন্তু কোথাকার? ঢাকার আড়াইটা আর লন্ডনের আড়াইটা দুটি আলাদা মুহূর্ত। যে datetime-এর সাথে কোনো time zone লেখা নেই, তাকে বলে naive datetime — “সরল”, কারণ সে জানে না সে কোথাকার। যার সাথে time zone জোড়া আছে, সে aware datetime — একটি নির্দিষ্ট, দ্ব্যর্থহীন মুহূর্ত।

সারা পৃথিবীর সময় মাপা হয় একটি মূল ঘড়ি থেকে — UTC. বাংলাদেশের মান সময় UTC-র চেয়ে ৬ ঘণ্টা এগিয়ে (UTC+6). অর্থাৎ ঢাকায় দুপুর আড়াইটা মানে UTC-তে সকাল সাড়ে আটটা। শুধু বাংলাদেশের ভেতরে চলা একটি ছোট প্রোগ্রামে naive সময়ে সমস্যা হয় না, কারণ সবাই একই ঘড়ি ধরে নেয়। সমস্যা শুরু হয় যখন:

  • আপনার সার্ভার বিদেশের কোনো data center-এ — সেখানে সার্ভারের ঘড়ি সাধারণত UTC-তে চলে, আর datetime.now() ঢাকার সময় দেয় না;
  • অ্যাপের ব্যবহারকারী একাধিক দেশে — প্রবাসী শ্রমিকের মোবাইল ব্যাংকিং অ্যাপ, যেখানে একজন দুবাইয়ে, আরেকজন ঢাকায়;
  • যে দেশে বছরে দুবার ঘড়ি এক ঘণ্টা এগোনো-পেছানো হয় (daylight saving time) — তখন একই স্থানীয় সময় দুবার আসতে পারে, বা একেবারে না-ও আসতে পারে।

Python 3.9 থেকে standard library-তে zoneinfo module আছে। এটি পৃথিবীর সব অঞ্চলের নিয়ম জানে, নাম ধরে:

from datetime import datetime
from zoneinfo import ZoneInfo

dhaka = ZoneInfo("Asia/Dhaka")
meeting = datetime(2026, 11, 5, 14, 30, tzinfo=dhaka)          # aware
print(meeting.isoformat())                         # 2026-11-05T14:30:00+06:00
print(meeting.astimezone(ZoneInfo("Europe/London")))  # একই মুহূর্ত, লন্ডনের ঘড়িতে
print(meeting.astimezone(ZoneInfo("UTC")))         # 2026-11-05 08:30:00+00:00

+06:00 অংশটি বলে দিচ্ছে, এই সময় UTC থেকে ৬ ঘণ্টা এগিয়ে। astimezone() মুহূর্তটি বদলায় না, শুধু অন্য ঘড়িতে দেখায় — ঠিক যেমন একই টাকার অঙ্ক টাকায় বা ডলারে লেখা যায়। নিয়ম মনে রাখার সহজ উপায়: ভেতরে সংরক্ষণ আর হিসাব করুন UTC-তে, মানুষকে দেখানোর সময় তার স্থানীয় time zone-এ বদলান। বর্তমান মুহূর্ত aware রূপে পেতে datetime.now(dhaka) বা datetime.now(timezone.utc).

Windows-এ ZoneInfoNotFoundError আসতে পারে। zoneinfo প্রথমে অপারেটিং সিস্টেমের time zone ডেটাবেস খোঁজে। Linux আর macOS-এ সেটি সাধারণত থাকে, কিন্তু Windows-এ এই ফরম্যাটে থাকে না — তখন ZoneInfo("Asia/Dhaka") লিখলেই error. সমাধান: virtual environment-এ pip install tzdata (পর্ব ২৩). এটি Python-এর মূল দলেরই রাখা একটি package, যা একই ডেটাবেস দেয়। এছাড়া naive আর aware datetime পরস্পর তুলনা বা বিয়োগ করা যায় না — TypeError: can't compare offset-naive and offset-aware datetimes. একটি প্রোগ্রামে একটি ধরনই বেছে নিন।

বাংলাদেশের মান সময় সাধারণত সারা বছর একই থাকে, কোনো daylight saving নেই। তবে একটি ব্যতিক্রমী অধ্যায় আছে: ২০০৯ সালে বিদ্যুৎ সাশ্রয়ের উদ্দেশ্যে জুনের মাঝামাঝি সময়ে ঘড়ি এক ঘণ্টা এগিয়ে দেওয়া হয়েছিল (UTC+7), আর সেই বছরের শেষে আবার আগের সময়ে ফেরানো হয়। পরের বছর থেকে ব্যবস্থাটি আর চালু হয়নি। মজার ব্যাপার হলো, zoneinfo যে ডেটাবেস ব্যবহার করে, তাতে এই কয়েক মাসের ইতিহাসও লেখা আছে — তাই Asia/Dhaka-তে ২০০৯ সালের জুলাইয়ের একটি সময় দিলে সঠিকভাবেই +07:00 দেখায়। অতীতের লেনদেনের হিসাব মেলাতে এমন খুঁটিনাটিই গুরুত্বপূর্ণ।

৫ · random আর secrets — লটারির পাশা বনাম তালার চাবি

লুডুর ছক্কা, কুইজের প্রশ্ন এলোমেলো করা, পরীক্ষামূলক ডেটা বানানো, গেমে শত্রুর অবস্থান — এসবের জন্য random module. কিন্তু একটি জিনিস বুঝে রাখা জরুরি: কম্পিউটার একটি নির্দিষ্ট নিয়মে চলা যন্ত্র; সে সত্যিকারের এলোমেলো কিছু বানাতে পারে না। random যা দেয়, তা pseudo-random — একটি গাণিতিক সূত্র থেকে আসা সংখ্যার সারি, যা দেখতে এলোমেলো, কিন্তু শুরুর মান জানলে পুরো সারিটি আগে থেকেই বলে দেওয়া যায়।

সেই শুরুর মানের নাম seed. random.seed(42) লিখলে প্রতিবার একই সারি আসে। এটি দুর্বলতা নয়, একটি বড় সুবিধা: ধরুন একটি সিমুলেশনে বাগ দেখা দিল — seed স্থির থাকলে আপনি ঠিক একই ঘটনা বারবার ঘটিয়ে বাগটি খুঁজতে পারেন। মেশিন লার্নিংয়ে মডেলের ফল পুনরাবৃত্তিযোগ্য রাখতেও seed বসানো হয়। seed না দিলে Python শুরুতে অপারেটিং সিস্টেম থেকে একটি এলোমেলো মান নিয়ে নিজেই seed করে, তাই প্রতি রানে আলাদা ফল আসে।

ফাংশনকী করেবাস্তব ব্যবহার
randint(a, b)a থেকে b পর্যন্ত একটি পূর্ণসংখ্যা, দুই প্রান্তসহলুডুর ছক্কা: randint(1, 6)
random()0 থেকে 1-এর মাঝে একটি float (1 বাদে)সম্ভাবনা: ৩০% সময় বৃষ্টি
choice(seq)সারি থেকে একটি জিনিসআজকের “দিনের প্রশ্ন”
sample(seq, k)kটি আলাদা জিনিস, পুনরাবৃত্তি ছাড়ালটারিতে ৩ জন বিজয়ী
choices(seq, k=n)nটি জিনিস, একই জিনিস একাধিকবার আসতে পারেপরীক্ষামূলক ডেটা বানানো
shuffle(lst)list-কে জায়গায় এলোমেলো করে, ফেরত দেয় Noneকুইজের প্রশ্নের ক্রম বদলানো

shuffle নিয়ে একটি সতর্কতা: এটি list-কে জায়গায় বদলায় আর None ফেরত দেয় — পর্ব ১৪-এর sort()-এর মতো। q = random.shuffle(questions) লিখলে q হবে None. নতুন এলোমেলো list চাইলে random.sample(questions, len(questions)).

নিরাপত্তার কাজে কখনো random নয় — secrets

এখন ভাবুন bKash-এর মতো একটি সেবার কথা, যেখানে লগইনের সময় ৬ অঙ্কের OTP পাঠানো হয়। কেউ যদি random.randint(100000, 999999) দিয়ে OTP বানায়, তাহলে বিপদ। random-এর পেছনের অ্যালগরিদম (Mersenne Twister) দ্রুত আর পরিসংখ্যানে চমৎকার, কিন্তু নিরাপত্তার জন্য তৈরি নয়: যথেষ্ট পরিমাণ আউটপুট দেখলে এর ভেতরের অবস্থা হিসাব করে বের করা যায়, আর তখন পরের সব “এলোমেলো” সংখ্যা আগাম জানা যায় (প্রশ্ন ০২-এ বিস্তারিত)।

এজন্য Python 3.6 থেকে আছে secrets module. এটি অপারেটিং সিস্টেমের নিরাপদ উৎস থেকে সংখ্যা নেয়, যাকে বলে CSPRNG — এমনভাবে বানানো, যাতে আগের আউটপুট দেখে পরেরটি অনুমান করা কার্যত অসম্ভব।

import secrets
import string

otp = f"{secrets.randbelow(10**6):06d}"            # ৬ অঙ্কের OTP, যেমন 042917
token = secrets.token_urlsafe(32)                  # password reset লিংকের জন্য
alphabet = string.ascii_letters + string.digits
password = "".join(secrets.choice(alphabet) for _ in range(12))

:06d নিশ্চিত করে যে ছোট সংখ্যাও সামনে শূন্য বসিয়ে ৬ অঙ্কের হয় (পর্ব ১০-এর format spec). এই কোডের ফল প্রতিবার আলাদা, আর সেটাই উদ্দেশ্য — তাই এখানে কোনো আউটপুট দেখানো হলো না।

মূল নিয়ম

random = লটারির পাশা — খেলা, সিমুলেশন, টেস্ট ডেটা, আর seed দিয়ে পুনরাবৃত্তিযোগ্য পরীক্ষা। secrets = তালার চাবি — OTP, password, token, session ID, কোনো কিছু যা অনুমান করা গেলে কারও ক্ষতি হয়। সন্দেহ হলে secrets; এর খরচ প্রায় নেই।

এবার এখন পর্যন্ত দেখা datetime আর random একসাথে চালিয়ে দেখুন। লটারির অংশে seed বসানো আছে — টেস্টের জন্য — তাই আপনিও ঠিক একই বিজয়ী পাবেন:

from datetime import date, datetime, timedelta
import random

today = date(2026, 10, 17)                 # স্থির "আজ" — উদাহরণের জন্য
exam = date.fromisoformat("2026-12-16")
print((exam - today).days, "দিন বাকি")
print(date(2026, 1, 20) + timedelta(days=30))

d = datetime.strptime("05/11/2026 14:30", "%d/%m/%Y %H:%M")
print(d.strftime("%A, %d %B %Y"), d.isoformat())
print(d.weekday(), d.tzinfo)

random.seed(42)                            # শুধু টেস্টের জন্য
tickets = list(range(101, 111))
print(random.randint(1, 6), random.choice(tickets))
print(random.sample(tickets, 3))
random.shuffle(tickets)
print(tickets)

প্রথম লাইন 60 দিন বাকি — ১৭ অক্টোবর থেকে ১৬ ডিসেম্বর: অক্টোবরের বাকি ১৪, নভেম্বরের ৩০, ডিসেম্বরের ১৬. দ্বিতীয় লাইন 2026-02-19 — ধাপে ধাপে ছবির সেই ফল; print তারিখকে ISO ধাঁচে দেখায়। তৃতীয় লাইনে strptime দিন/মাস/বছরের লেখাটি পড়েছে, আর strftime দেখিয়েছে Thursday, 05 November 2026 2026-11-05T14:30:00 — বারের নাম datetime নিজেই হিসাব করেছে। চতুর্থ লাইন 3 None: weekday() সোমবারকে 0 ধরে, তাই বৃহস্পতিবার 3; আর tzinfo হলো None, অর্থাৎ এটি naive datetime. শেষ তিন লাইন seed 42-এর ফল — CPython-এ প্রতিবার একই: 6 102, তারপর [101, 105, 104], আর এলোমেলো করা টিকিট [110, 108, 109, 101, 105, 107, 106, 102, 103, 104]. seed বদলালে বা মুছে দিলে এই ফলগুলো বদলে যাবে।

৬ · collections, heapq আর bisect — ডেটা স্ট্রাকচারের বাড়তি যন্ত্র

পর্ব ১৪–১৭-এ list, tuple, dict আর set শিখেছেন। বেশিরভাগ কাজ এগুলো দিয়েই হয়। কিন্তু কিছু প্যাটার্ন এত বারবার আসে যে collections module তাদের জন্য বিশেষ টাইপ দিয়েছে। সবগুলো dict, tuple বা list-এর আত্মীয়, তাই ব্যবহার করা সহজ।

টাইপকোন সমস্যায়এক লাইনে
Counterকোনটি কতবার — গণনাCounter(sales).most_common(3)
defaultdictদলে ভাগ, না থাকা চাবিতে নিজে মান বসানোdefaultdict(list)
dequeদুই প্রান্তে দ্রুত যোগ-বিয়োগ, “শেষ N টি”deque(maxlen=5)
namedtupleনাম দিয়ে পড়া যায় এমন tuplePoint(x=3, y=4).x — পর্ব ১৫
OrderedDictক্রম নিয়ে বাড়তি কাজ (move_to_end)আজ সাধারণ dict-ই ক্রম রাখে
ChainMapকয়েকটি dict স্তরে স্তরে একসাথে খোঁজাChainMap(user_cfg, defaults)

Counter — দোকানের সবচেয়ে বেশি বিক্রি

পর্ব ১৬-এ হাতে লুপ লিখে শব্দ গুনেছিলেন — counts.get(word, 0) + 1. Counter এই পুরো কাজটি এক ডাকে করে, সাথে কয়েকটি বাড়তি সুবিধা দেয়: না থাকা চাবি পড়লে KeyError নয়, 0 দেয়; most_common(n) সবচেয়ে বেশি আসা nটি দেয়; আর দুটি Counter যোগ-বিয়োগও করা যায় (গতকালের বিক্রি + আজকের বিক্রি)। দেখুন ভেতরে কী ঘটে:

1words = "কাল ক্লাস কাল পরীক্ষা কাল".split()2c = Counter(words)3print(c.most_common(2), c["গণিত"])
Counter ভেতরে যা করে (সবুজ লাইন চলছে)
এখন যে শব্দ
c
  1. শব্দের list, খালি Counter. split() ফাঁকা জায়গায় ভেঙে ৫টি শব্দের list দিল। Counter(words) ডাকার সাথে সাথে ভেতরে একটি খালি গণনার খাতা তৈরি হয় — Counter আসলে dict-এর একটি subclass, তাই খাতাটি একটি dict: চাবি শব্দ, মান গণনা।
  2. “কাল” — প্রথমবার। চাবি ছিল না; Counter নিজেই 0 ধরে 1 যোগ করল। পর্ব ১৬-এর হাতে লেখা get(word, 0) + 1 এখানে ভেতরে লুকানো।
  3. “ক্লাস” — নতুন শব্দ। আরেকটি চাবি, গণনা 1. চাবিগুলো প্রথম দেখার ক্রমে বসছে — এই ক্রম শেষ ধাপে গুরুত্বপূর্ণ হবে।
  4. “কাল” — আবার। চাবি আছে, গণনা 1 থেকে 2. কোনো if নেই, কোনো KeyError-এর ভয় নেই।
  5. “পরীক্ষা” — নতুন। তৃতীয় চাবি, গণনা 1. এখন ক্লাস আর পরীক্ষা দুজনেরই গণনা সমান — 1.
  6. “কাল” — তৃতীয়বার। গণনা 3. পুরো লুপটি C-তে লেখা, তাই লাখো শব্দেও হাতে লেখা Python লুপের চেয়ে সাধারণত দ্রুত।
  7. most_common(2) আর না থাকা চাবি। সবচেয়ে বেশি আসা দুটি: ('কাল', 3), তারপর ('ক্লাস', 1). ক্লাস আর পরীক্ষার গণনা সমান, তবু ক্লাস এল — কারণ সমান গণনায় যে আগে দেখা গেছে সে আগে, এটি ডকুমেন্টেশনে লেখা নিয়ম। আর c["গণিত"] — যে শব্দ কখনো আসেনি — KeyError নয়, 0 দিল।

deque — শেষ ৫টি লেনদেন

মোবাইল ব্যাংকিং অ্যাপে “সাম্প্রতিক লেনদেন” অংশে শুধু শেষ কয়েকটি দেখায়। list দিয়ে করলে প্রতিবার নতুন লেনদেন যোগ করে পুরোনোটি pop(0) দিয়ে সরাতে হয় — আর পর্ব ১৪-এ দেখেছেন, list-এর শুরু থেকে সরানো O(n), কারণ পরের সবাইকে এক ঘর সরাতে হয়। deque (উচ্চারণ “ডেক”, double-ended queue) দুই প্রান্তেই যোগ-বিয়োগ করে O(1)-এ: append/pop ডানে, appendleft/popleft বাঁয়ে। আর maxlen দিলে সে নিজেই পুরোনোগুলো ফেলে দেয়:

1last3 = deque(maxlen=3)2for amount in [500, 120, 900, 50, 300]:3    last3.append(amount)
নাম → মান (সবুজ লাইন এইমাত্র চলেছে)
amount
last3
বাদ পড়ল
  1. সীমা বেঁধে দেওয়া। deque(maxlen=3) — এই deque কখনো ৩টির বেশি জিনিস রাখবে না। এটিকে একটি ছোট বেঞ্চের মতো ভাবুন, যেখানে তিনজনই বসতে পারে।
  2. ৫০০ টাকা। প্রথম লেনদেন ডান প্রান্তে বসল। list-এর append-এর মতোই।
  3. ১২০ টাকা। দ্বিতীয়টিও ডানে। ক্রম বজায় আছে — পুরোনো বাঁয়ে, নতুন ডানে।
  4. ৯০০ টাকা — বেঞ্চ পূর্ণ। এখন তিনটি জিনিস, সীমায় পৌঁছেছে। এ পর্যন্ত কিছু বাদ পড়েনি।
  5. ৫০ টাকা — সবচেয়ে পুরোনো বিদায়। জায়গা নেই, তাই deque নিজেই বাঁ প্রান্ত থেকে 500 ফেলে দিল, আর ডানে 50 বসাল। কোনো if len(...) > 3 লিখতে হয়নি, কোনো O(n) সরানোও হয়নি।
  6. ৩০০ টাকা। আবার একই — 120 বাদ, 300 যোগ। শেষে থাকল শুধু সাম্প্রতিক তিনটি: [900, 50, 300]. লগ ফাইলের শেষ কয়েক লাইন, সেন্সরের শেষ কয়েকটি মাপ, চলমান গড় (moving average) — সবখানে এই প্যাটার্ন।

deque-এর দুর্বলতাও জানা দরকার: মাঝখানের জিনিস index দিয়ে পড়া (dq[500]) list-এর মতো তাৎক্ষণিক নয়। কাজটি তখনই deque-এর, যখন যোগ-বিয়োগ শুধু দুই প্রান্তে — কিউ (queue), সাম্প্রতিক ইতিহাস, পর্ব ৪৫-এর BFS অ্যালগরিদম। ভেতরের গঠনের গল্প প্রশ্ন ০৫-এ।

OrderedDict আর ChainMap — দুটি ছোট কথা

Python 3.7-এর আগে সাধারণ dict ক্রম মনে রাখত না, তাই OrderedDict খুব প্রচলিত ছিল। এখন সাধারণ dict-ই ক্রম রাখে (পর্ব ১৬), তাই পুরোনো কোডে OrderedDict দেখলে ভয় পাবেন না — বেশিরভাগ ক্ষেত্রে সাধারণ dict দিয়েই চলত। এর বাড়তি সুবিধা এখনো আছে: move_to_end() দিয়ে একটি চাবি শেষে পাঠানো, আর দুটি OrderedDict-এর তুলনায় ক্রমও মেলানো হয়। ChainMap কয়েকটি dict-কে স্তরে স্তরে সাজায়: ChainMap(user_settings, defaults)-এ কোনো চাবি খুঁজলে আগে ব্যবহারকারীর সেটিংসে খোঁজে, না পেলে default-এ। কোনো কপি হয় না। কনফিগারেশন সামলাতে এটি কাজের।

heapq — সবচেয়ে বড় k টি; bisect — সাজানো list-এ সঠিক জায়গা

এক লাখ বিক্রির মধ্যে সবচেয়ে বড় ৫টি চাই। পুরো list সাজিয়ে (sorted(...)[-5:]) নেওয়া যায়, কিন্তু তাতে সবকিছু সাজানোর খরচ। heapq module একটি heap ব্যবহার করে — এমন একটি গাছ-আকৃতির কাঠামো (list-এর ভেতরেই রাখা), যেখানে সবচেয়ে ছোট জিনিসটি সবসময় সবার ওপরে থাকে। heapq.nlargest(5, sales) আর heapq.nsmallest(5, sales) এক লাইনে top-k দেয়, বড় ডেটায় সাধারণত পুরো সাজানোর চেয়ে কম খরচে। heap-এর আরেকটি বড় ব্যবহার priority queue — হাসপাতালের জরুরি বিভাগে আসার ক্রমে নয়, গুরুত্বের ক্রমে রোগী দেখা (heappush, heappop). Python-এর heap সবসময় min-heap — সবচেয়ে ছোটটি আগে বেরোয়।

bisect module সাজানো list-এ binary search করে (পর্ব ০২-এ দেখা ধারণা)। bisect.insort(scores, 62) একটি সংখ্যাকে এমন জায়গায় ঢোকায় যাতে list সাজানোই থাকে। আরেকটি সুন্দর ব্যবহার — গ্রেড বের করা: সীমাগুলো এক list-এ, limits = [33, 40, 50, 60, 70, 80], আর গ্রেডগুলো আরেকটিতে, grades = ["F", "D", "C", "B", "A-", "A", "A+"]. এবার bisect.bisect(limits, mark) বলে দেয় নম্বরটি কোন ধাপে পড়ে — ৭২ নম্বরের জন্য উত্তর 5, আর grades[5] হলো "A". লম্বা if/elif-এর দরকার হয় না।

C/C++ জানলে

C-এর standard library ছোট: সময়ের জন্য time.h, এলোমেলো সংখ্যার জন্য দুর্বল rand(), আর কোনো তৈরি hash table, deque বা database নেই। C++ অনেক বড় — <chrono>, <random>-এর engine ও distribution, std::deque, std::priority_queue. মূল পার্থক্য দর্শনে: C++-এর লাইব্রেরি মূলত গঠন আর অ্যালগরিদম দেয়, আর Python-এর লাইব্রেরি এর বাইরেও তৈরি “সমাধান” দেয় — SQL database, ইমেইল, zip, HTTP. C++-এর একই ধরনের ভ্রমণ দেখুন C++ কোর্সের লাইব্রেরি ভ্রমণ পর্বে।

৭ · os, sys, shutil আর statistics — প্রোগ্রাম আর মেশিনের মাঝের সেতু

environment variable — গোপন কথা কোডের বাইরে

প্রতিটি চলমান প্রোগ্রাম অপারেটিং সিস্টেম থেকে কিছু নাম-মান জোড়া পায় — environment variable. পর্ব ০৩-এ PATH দেখেছেন। বাস্তব প্রোগ্রামে এগুলোর বড় ব্যবহার হলো এমন জিনিস রাখা, যা কোডে লেখা উচিত নয়: database-এর password, API key, আর “এটি কি পরীক্ষার সার্ভার নাকি আসল সার্ভার” ধরনের সেটিং।

import os

mode = os.environ.get("APP_MODE", "development")   # না থাকলে default
api_key = os.environ["SMS_API_KEY"]                 # না থাকলে KeyError — থাকতেই হবে
print(os.getcwd())                                  # কোন ফোল্ডার থেকে চলছে

পর্ব ১৬-এর নিয়মটিই এখানে খাটে: os.environ একটি dict-এর মতো object. যে সেটিং না থাকলেও চলে, তার জন্য .get() আর একটি অর্থপূর্ণ default; যে জিনিস ছাড়া প্রোগ্রাম চলতেই পারে না (API key), তার জন্য [ ] — যাতে শুরুতেই পরিষ্কার error আসে, মাঝপথে রহস্যময় ব্যর্থতা নয়। password কোডে লিখে GitHub-এ তুলে দেওয়া বাস্তবে খুব প্রচলিত দুর্ঘটনা; পর্ব ৫০-এ এ নিয়ে বিস্তারিত।

sys — চলমান Python-এর নিজের কথা

  • sys.argv: টার্মিনালে python report.py 2026 ঢাকা লিখলে প্রোগ্রামের ভেতরে sys.argv হয় ['report.py', '2026', 'ঢাকা'] — প্রথমটি ফাইলের নাম, বাকিগুলো command-line argument, সবই string. ছোট স্ক্রিপ্টে এটুকুই যথেষ্ট; বড় টুলে argparse (পর্ব ৪৯)।
  • sys.exit(code): প্রোগ্রাম থামায় আর অপারেটিং সিস্টেমকে একটি exit code জানায় — প্রথা অনুযায়ী 0 মানে সফল, অন্য সংখ্যা মানে কোনো সমস্যা। অটোমেশন স্ক্রিপ্ট এই সংখ্যা দেখেই বোঝে পরের ধাপে যাবে কি না।
  • sys.version, sys.platform, sys.path: কোন Python, কোন অপারেটিং সিস্টেম, আর import করার সময় কোন কোন ফোল্ডারে module খোঁজা হয় (পর্ব ২২)। platform module আরও বিস্তারিত দেয় — প্রসেসরের ধরন, অপারেটিং সিস্টেমের version. এগুলোর মান প্রতিটি মেশিনে আলাদা, তাই ডিবাগিং রিপোর্টে কাজের।

shutil — ফাইল কপি, সরানো আর zip

পর্ব ২৫-এ pathlib দিয়ে ফাইল পড়া-লেখা আর পথ নিয়ে কাজ শিখেছেন। পুরো ফাইল বা ফোল্ডার কপি-সরানোর মতো “উচ্চ স্তরের” কাজের জন্য shutil: shutil.copy(src, dst), shutil.copytree (পুরো ফোল্ডার), shutil.move, আর shutil.make_archive("backup", "zip", "reports") — একটি ফোল্ডারকে এক লাইনে zip করা। AI প্রায়ই এসব কাজে os.system("cp ...") লেখে, যা Windows-এ চলে না আর ফাইলের নামে ফাঁকা জায়গা থাকলে ভাঙে; shutil সব অপারেটিং সিস্টেমে একইভাবে চলে। shutil.rmtree পুরো ফোল্ডার ফেরত আনার উপায় ছাড়াই মুছে দেয় — চালানোর আগে পথটি দুবার দেখুন।

statistics — গড়, মধ্যমা, প্রচুরক, বিচ্যুতি

Python 3.4 থেকে statistics module আছে। ছোট ডেটার জন্য NumPy-র মতো বড় package লাগে না:

  • mean([500, 120, 900, 50, 300]) → গড় 374
  • median(...) → মধ্যমা 300 — সাজালে মাঝের মান; কয়েকটি বিশাল লেনদেন গড়কে টেনে তোলে, মধ্যমাকে নয়। তাই “সাধারণ” আয় বোঝাতে মধ্যমা বেশি সৎ।
  • mode([3, 1, 3, 2]) → প্রচুরক 3 — সবচেয়ে বেশি আসা মান
  • stdev(...) আর pstdev(...) → নমুনা (sample) আর পুরো জনসংখ্যার (population) মানক বিচ্যুতি। কোনটি চাই, পরিসংখ্যানের প্রশ্ন; একই ডেটায় দুটির মান আলাদা।

৮ · sqlite3 আর ছোট রত্ন — একটি পুরো database, hash, uuid

পর্ব ২৬-এ CSV আর JSON-এ ডেটা রেখেছেন। ডেটা বাড়লে সমস্যা শুরু হয়: “৮০-র বেশি নম্বর পেয়েছে রাজশাহীর কারা” জানতে প্রতিবার পুরো ফাইল পড়তে হয়, দুটি প্রোগ্রাম একসাথে লিখলে ফাইল নষ্ট হতে পারে। এখানে আসে SQLite — একটি পূর্ণ SQL database, যা আলাদা কোনো সার্ভার ছাড়াই একটি মাত্র ফাইলে থাকে। আর এটি চালানোর sqlite3 module Python-এর সাথেই আসে।

প্রতিটি sqlite3 কাজ একই পাঁচ ধাপের পথ ধরে। নিচের ছবিতে একটি ছোট শিক্ষার্থী টেবিল দিয়ে দেখুন। ":memory:" মানে database থাকবে শুধু RAM-এ — প্রোগ্রাম শেষ হলেই মুছে যাবে; শেখা আর টেস্টের জন্য আদর্শ। আসল প্রোগ্রামে "school.db"-এর মতো একটি ফাইলের নাম দিন।

  1. connect":memory:"database খোলা
  2. CREATEstudentটেবিলের নকশা
  3. INSERT?, ?সারি ঢোকানো
  4. SELECTWHEREপ্রশ্ন করা
  5. fetchalllistউত্তর হাতে
con = sqlite3.connect(":memory:")একটি connection object
con = sqlite3.connect(":memory:")
# RAM-এ একটি নতুন, খালি database তৈরি হলো
# ফাইলের নাম দিলে: না থাকলে তৈরি, থাকলে খোলা
CREATE TABLEকলামের নাম আর ধরন
con.execute("CREATE TABLE student (name TEXT, marks INTEGER)")

student:
  name (TEXT) | marks (INTEGER)
  — এখনো কোনো সারি নেই —
executemany? চিহ্নে মান বসে — নিরাপদে
con.executemany("INSERT INTO student VALUES (?, ?)", rows)

  name   | marks
  রিয়া   | 78
  জয়    | 91
  সুমি   | 85
SELECTdatabase নিজে খোঁজে ও সাজায়
cur = con.execute(
  "SELECT name, marks FROM student
   WHERE marks >= ? ORDER BY marks DESC", (80,))
# রিয়া (78) বাদ; বাকি দুজন বড় থেকে ছোট
cur.fetchall()Python-এর list, ভেতরে tuple
cur.fetchall()
→ [('জয়', 91), ('সুমি', 85)]
con.close()   # কাজ শেষে বন্ধ
  1. connect — দরজা খোলা। sqlite3.connect() একটি connection দেয়, যার মাধ্যমে database-এর সাথে সব কথা হয়। কোনো সার্ভার চালু করতে হয়নি, কোনো username-password লাগেনি — পুরো database engine-টি আপনার প্রোগ্রামের ভেতরেই চলছে।
  2. CREATE TABLE — টেবিলের নকশা। SQL ভাষায় বলা হলো: student নামে একটি টেবিল, যার দুটি কলাম — নাম (লেখা) আর নম্বর (পূর্ণসংখ্যা)। একটি spreadsheet-এর শিরোনামের সারির মতো ভাবুন। SQL নিজেই একটি আলাদা ভাষা; এখানে শুধু ঝলক।
  3. INSERT — সারি ঢোকানো, ? দিয়ে। মানগুলো SQL লেখার ভেতরে f-string দিয়ে জোড়া হয়নি — বসানো হয়েছে ? চিহ্নের জায়গায়, আলাদা করে। একে বলে parameterized query. এতে কেউ নামের ঘরে দুষ্ট SQL লিখে দিলেও (SQL injection) তা শুধু একটি নাম হিসেবেই থাকে, কমান্ড হিসেবে চলে না।
  4. SELECT — প্রশ্ন করা। “৮০ বা বেশি যাদের, তাদের নাম আর নম্বর, বড় থেকে ছোট” — খোঁজা আর সাজানো দুটোই database নিজে করে। লাখো সারিতেও এটি দ্রুত, বিশেষ করে কলামে index থাকলে। ফল আসে একটি cursor-এ — উত্তরের সারিগুলোর ওপর একটি আঙুলের মতো।
  5. fetchall — উত্তর Python-এ। fetchall() সব সারি একটি list-এ দেয়, প্রতিটি সারি একটি tuple. বড় ফলাফলে একসাথে সব না এনে cursor-এর ওপর for লুপ চালানো যায়। কাজ শেষে close(). ফাইলের database-এ পরিবর্তন স্থায়ী করতে con.commit() লাগে — অথবা with con: ব্লক ব্যবহার করুন, যা সফল হলে নিজে commit করে।

এবার এই পর্বের collections, heapq, statistics আর sqlite3 একসাথে — একটি চায়ের দোকানের দিনের হিসাব আর একটি ছোট শিক্ষার্থী টেবিল:

from collections import Counter, deque
import heapq, sqlite3, statistics

sales = ["চা", "বিস্কুট", "চা", "কলা", "চা", "বিস্কুট", "পানি"]
print(Counter(sales).most_common(2))

amounts = [500, 120, 900, 50, 300]
last3 = deque(maxlen=3)
for a in amounts:
    last3.append(a)
print(list(last3), heapq.nlargest(2, amounts))
print(statistics.mean(amounts), statistics.median(amounts))

con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE student (name TEXT, marks INTEGER)")
con.executemany("INSERT INTO student VALUES (?, ?)",
                [("রিয়া", 78), ("জয়", 91), ("সুমি", 85)])
query = "SELECT name, marks FROM student WHERE marks >= ? ORDER BY marks DESC"
print(con.execute(query, (80,)).fetchall())
con.close()

প্রথম লাইন [('চা', 3), ('বিস্কুট', 2)] — দোকানের সবচেয়ে বেশি বিক্রি হওয়া দুটি জিনিস, গণনাসহ। দ্বিতীয় লাইন [900, 50, 300] [900, 500]: deque শুধু শেষ তিনটি লেনদেন রেখেছে (ধাপে ধাপে ছবির ফল), আর nlargest পুরো list থেকে সবচেয়ে বড় দুটি দিয়েছে — 500 deque থেকে বাদ পড়লেও মূল list-এ আছে। তৃতীয় লাইন 374 300 — গড় 374 (সব int বলে ফলও int), মধ্যমা 300. লক্ষ করুন, একটি ৯০০ টাকার লেনদেন গড়কে মধ্যমার চেয়ে অনেক ওপরে তুলেছে। শেষ লাইন database-এর উত্তর [('জয়', 91), ('সুমি', 85)] — রিয়া ৮০-র কম বলে বাদ, বাকিরা নম্বরের ক্রমে। database RAM-এ ছিল, তাই close()-এর পর কোনো ফাইল পড়ে থাকে না।

আরও কয়েকটি ছোট রত্ন

🔒 hashlib

যেকোনো ডেটার একটি “আঙুলের ছাপ” — cryptographic hash. একই লেখা থেকে সবসময় একই ছাপ, এক অক্ষর বদলালে সম্পূর্ণ আলাদা। ফাইল ডাউনলোড ঠিকঠাক হলো কি না যাচাইয়ে কাজের। (password রাখতে সরাসরি sha256 নয় — পর্ব ৫০.)

hashlib.sha256(b"...").hexdigest()

🆔 uuid

uuid.uuid4() একটি ১২৮-bit এলোমেলো পরিচয় দেয় — অর্ডার নম্বর, ফাইলের অনন্য নাম। দুটি মিলে যাওয়ার সম্ভাবনা কার্যত শূন্য, কোনো কেন্দ্রীয় কাউন্টার ছাড়াই।

uuid.uuid4()

📝 textwrap

লম্বা লেখাকে নির্দিষ্ট প্রস্থে ভাঙা (wrap, fill), ছোট করে “…” বসানো (shorten), কোডের ভেতরের লেখা থেকে বাড়তি indent সরানো (dedent).

textwrap.fill(text, 40)

🔤 string

তৈরি অক্ষরের তালিকা — string.digits, string.ascii_letters, string.punctuation. password বানাতে, ইনপুট যাচাইয়ে কাজের।

string.digits

hash কীভাবে কাজ করে, একটি উদাহরণে দেখুন — এর ফল নির্ধারিত, তাই যেকোনো কম্পিউটারে একই আসবে:

>>> import hashlib
>>> hashlib.sha256("ABCL TECH".encode()).hexdigest()
'3e8e9fd76e3152fbdc4a35b41b3b12595fe5d5e70eaa2f472bb7554f59d97a2d'
>>> hashlib.sha256("abcl tech".encode()).hexdigest()
'8845b3ad352a1a1f810db6d2b7d4464a6c92075b3065355cb42b7765ad8593f4'

শুধু বড় হাত-ছোট হাত বদলেছে, অথচ ছাপ সম্পূর্ণ আলাদা। .encode() দরকার, কারণ hash কাজ করে bytes-এর ওপর, string-এর ওপর নয় (পর্ব ০৯)। পর্ব ১৬-এর hash()-এর সাথে গুলিয়ে ফেলবেন না: string-এর ক্ষেত্রে সেটি প্রতি রানে বদলায় আর dict-এর ভেতরের কাজে লাগে; hashlib-এর ছাপ চিরকাল একই, আর তা নিরাপত্তার কাজের জন্য তৈরি।

module খুঁজবেন কোথায়, আর কখন third-party

নতুন কোনো সমস্যায় প্রথম প্রশ্ন: “standard library-তে কি এর জন্য কিছু আছে?” খোঁজার জায়গা:

  • docs.python.org-এর “Library Reference”: সব module-এর অফিসিয়াল তালিকা, পরিবার অনুযায়ী সাজানো। প্রতিটি পাতার ওপরে লেখা থাকে কোন version-এ কী যোগ হয়েছে (“Added in version 3.9”) — AI-এর দেওয়া কোড আপনার version-এ চলবে কি না যাচাইয়ে এটি খুব কাজের।
  • REPL-এ নিজে: help("collections"), dir(statistics), আর import this-এর মতো মজার ঝলক। sys.stdlib_module_names (Python 3.10 থেকে) সব standard module-এর নাম দেয়।
  • PyMOTW — Python Module of the Week: Doug Hellmann-এর লেখা দীর্ঘদিনের জনপ্রিয় সাইট, যেখানে প্রতিটি module উদাহরণসহ বোঝানো।

তবে standard library সবকিছুর সেরা উত্তর নয়। এর কোড বদলায় ধীরে — নতুন Python version আসে বছরে একবার, আর পুরোনো আচরণ ভাঙা যায় না। তাই কিছু ক্ষেত্রে PyPI-এর third-party package বাস্তবে আদর্শ হয়ে গেছে:

কাজstandard libraryপ্রচলিত third-partyকখন কোনটি
HTTP অনুরোধurllib.requestrequests, httpxছোট স্ক্রিপ্টে urllib চলে; বাস্তব API কাজে requests অনেক সহজ (পর্ব ৪৭)
সংখ্যার বড় হিসাবstatistics, mathnumpy, pandasকয়েকশ মানে statistics; লাখো সারির টেবিলে pandas (পর্ব ৪৬)
টেস্টunittestpytestpytest কম লেখায় বেশি কাজ (পর্ব ৩৯)
time zonezoneinfopytz (পুরোনো)Python 3.9+ হলে zoneinfo; নতুন কোডে pytz-এর দরকার নেই
বড় databasesqlite3PostgreSQL ড্রাইভার, ORMএকজন ব্যবহারকারী বা ছোট সাইটে SQLite; অনেকে একসাথে লিখলে সার্ভার database

সিদ্ধান্তের একটি সহজ নিয়ম: standard library দিয়ে যদি কাজটি পরিষ্কারভাবে হয়, তবে বাড়তি dependency আনবেন না — প্রতিটি package মানে আরেকটি জিনিস যা হালনাগাদ রাখতে হয়, যার নিরাপত্তা দেখতে হয়। আর যদি standard library-র রূপটি স্পষ্টতই কষ্টকর হয়, আর third-party-টি ব্যাপকভাবে ব্যবহৃত ও নিয়মিত রক্ষণাবেক্ষণ করা — তবে সেটিই বুদ্ধিমানের।

পরীক্ষা ও ইন্টারভিউতে
  • OTP বা password বানাতে কোন module? — secrets; random-এর আউটপুট অনুমানযোগ্য, নিরাপত্তার জন্য নয়।
  • naive আর aware datetime-এর পার্থক্য? — naive-এর সাথে time zone নেই (tzinfo is None); aware-এর আছে, তাই সে একটি দ্ব্যর্থহীন মুহূর্ত।
  • queue বানাতে list নয় কেন deque? — list-এর শুরু থেকে pop(0) O(n); deque দুই প্রান্তেই O(1).
  • random.seed() কেন দেওয়া হয়? — একই এলোমেলো সারি বারবার পেতে, যাতে টেস্ট আর পরীক্ষা পুনরাবৃত্তিযোগ্য হয়।
  • sqlite3-এ মান বসাতে f-string নয় কেন ?? — parameterized query SQL injection ঠেকায়, আর ধরন সামলানোও ঠিক থাকে।

৯ · AI-এর যুগে — standard library জানা মানুষই AI-এর ভুল ধরে

AI কোড লেখার সময় standard library প্রায়ই ঠিক ব্যবহার করে — কিন্তু কয়েকটি ভুল বারবার দেখা যায়। কোনোটি নিরাপত্তার গর্ত, কোনোটি সার্ভারে গিয়ে ধরা পড়া সময়ের বাগ, কোনোটি শুধু অকারণে লম্বা কোড বা বাড়তি package.

AI প্রায়ই যা লেখেসমস্যাভালো রূপ
random.randint(100000, 999999) দিয়ে OTPঅনুমানযোগ্য — নিরাপত্তার গর্তsecrets.randbelow(10**6)
datetime.now() বা datetime.utcnow() — time zone ছাড়াসার্ভারে অন্য ঘড়ি; utcnow() Python 3.12 থেকে deprecateddatetime.now(ZoneInfo("Asia/Dhaka")) বা datetime.now(timezone.utc)
হাতে লেখা গণনার dict আর লুপলম্বা, ভুলের সুযোগCounter, defaultdict
pip install pytz বা ছোট কাজে বড় packageঅকারণ dependencyzoneinfo, statistics, sqlite3 আগে দেখুন
list-এ pop(0) দিয়ে queueবড় ডেটায় ধীরdeque.popleft()
SQL-এ f-string দিয়ে মান জোড়াSQL injection? দিয়ে parameterized query
API key সরাসরি কোডে লেখাGitHub-এ ফাঁস হওয়ার ঝুঁকিos.environ থেকে পড়া

AI-কে এমন কোড লিখতে বলার সময় এই শর্তগুলো জুড়ে দিন:

আমি Python 3.12 বা নতুন version ব্যবহার করি। একটি ছোট কোচিং সেন্টারের জন্য স্ক্রিপ্ট লেখো: (১) "dd/mm/yyyy" রূপে দেওয়া পরীক্ষার তারিখ থেকে কত দিন বাকি তা বের করো, (২) শিক্ষার্থীদের লগইনের জন্য ৬ অঙ্কের OTP বানাও, (৩) ভর্তির তালিকা থেকে সবচেয়ে জনপ্রিয় ৩টি বিষয় দেখাও, (৪) শিক্ষার্থীদের একটি SQLite টেবিলে রাখো। শর্ত: ১. শুধু standard library ব্যবহার করবে; কোনো pip package লাগলে আগে কারণ বলবে। ২. OTP-র জন্য secrets ব্যবহার করবে, random নয়। ৩. সময় লাগলে zoneinfo দিয়ে "Asia/Dhaka" time zone-সহ aware datetime ব্যবহার করবে; datetime.utcnow() লিখবে না। ৪. "আজ"-এর তারিখ ফাংশনে argument হিসেবে নেবে, যাতে স্থির তারিখ দিয়ে টেস্ট করা যায়। ৫. গণনায় collections.Counter, SQL-এ শুধু ? দিয়ে parameterized query. ৬. প্রতিটি module কোন Python version থেকে আছে, এক লাইনে comment দাও।

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

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

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

প্র ০১“batteries included” যদি এত ভালো হয়, তবে Python 3.13 কেন standard library থেকে প্রায় বিশটি module সরিয়ে দিল? “মরা ব্যাটারি” বলতে কী বোঝায়, আর বড় লাইব্রেরির দাম কে দেয়?

Python-এর জনপ্রিয়তার একটি বড় কারণ ছিল তার সাথে আসা বিশাল লাইব্রেরি। ১৯৯০ আর ২০০০-এর দশকে ইন্টারনেট ধীর ছিল, package ইনস্টল করা ঝামেলার ছিল, আর অনেক অফিসের কম্পিউটারে বাইরের সফটওয়্যার আনতে অনুমতি লাগত। তখন “Python আছে মানেই ইমেইল পাঠানো, zip খোলা, ওয়েব সার্ভার চালানো সব আছে” — এটি ছিল বিশাল সুবিধা। সেই যুগের প্রয়োজনে অনেক module যোগ হয়েছিল: পুরোনো অডিও ফরম্যাট পড়ার, পুরোনো ইউনিক্স পাসওয়ার্ড ব্যবস্থার, CGI-এর মতো ওয়েব প্রযুক্তির, telnet-এর মতো প্রোটোকলের।

কিন্তু standard library-তে থাকা মানে একটি প্রতিশ্রুতি: প্রতিটি নতুন Python version-এ এটি চলবে, বাগ ঠিক হবে, নিরাপত্তার সমস্যা সারানো হবে। এই কাজ করেন মূলত স্বেচ্ছাসেবী core developer-রা। একটি module যত কম মানুষ ব্যবহার করে, তার রক্ষণাবেক্ষণকারী খুঁজে পাওয়াও তত কঠিন — অথচ তার নিরাপত্তার দায় থেকেই যায়। সময়ের সাথে কিছু module “মরা ব্যাটারি” হয়ে গেল: যে প্রযুক্তির জন্য বানানো, সেটিই প্রায় বিলুপ্ত, আর ভালো বিকল্প অন্যত্র আছে।

২০১৯ সালে Christian Heimes আর Brett Cannon প্রস্তাব করেন PEP 594 — “Removing dead batteries from the standard library”. পরিকল্পনা ছিল ধীরে, সতর্কভাবে: আগে কয়েক version ধরে সতর্কবার্তা (deprecation), তারপর সরানো। এই অনুযায়ী Python 3.11-এ module-গুলো deprecated হয়, আর Python 3.13-এ সরানো হয় — যার মধ্যে আছে cgi, cgitb, telnetlib, nntplib, crypt, imghdr, audioop, uu, pipes ইত্যাদি। (একই PEP-এর asyncore, asynchat, smtpd সরানো হয় আরও আগে, Python 3.12-এ; আর distutils একটি আলাদা PEP অনুযায়ী 3.12-এ সরে।) কিছু module-এর কাজ এখন PyPI-এর আলাদা package করে, যাদের নিজস্ব রক্ষণাবেক্ষণকারী আছে।

এখান থেকে একটি বাস্তব শিক্ষা: পুরোনো টিউটোরিয়াল বা AI-এর কোডে import cgi বা import telnetlib দেখলে, Python 3.13 বা নতুন version-এ সেটি ModuleNotFoundError দেবে। তখন ডকুমেন্টেশনে “Removed in 3.13” অংশ দেখে বিকল্প খুঁজুন। standard library বড় হওয়া আর ছোট হওয়া — দুটোই Python সম্প্রদায়ের একটি চলমান ভারসাম্য: সুবিধা বনাম রক্ষণাবেক্ষণের বোঝা।

মূল উপলব্ধি: প্রতিটি কোডের লাইন একটি দায় — কেউ না কেউ তাকে বাঁচিয়ে রাখে। “বাক্সে সব আছে” দর্শন শক্তিশালী, কিন্তু বাক্সও মাঝে মাঝে গোছাতে হয়। কোনো module ব্যবহারের আগে দেখে নিন সেটি এখনো জীবিত কি না।

প্র ০২random module-এর পেছনে Mersenne Twister নামের অ্যালগরিদম। এটি পরিসংখ্যানে চমৎকার হলেও নিরাপত্তার জন্য কেন অচল? Python-এ আলাদা secrets module কেন আনতে হলো?

Mersenne Twister (MT19937) ১৯৯৭ সালে জাপানের দুই গবেষক Makoto Matsumoto আর Takuji Nishimura তৈরি করেন। এর পর্যায়কাল (period) 219937 − 1 — অর্থাৎ সারিটি নিজেকে পুনরাবৃত্তি করার আগে এত বেশি সংখ্যা দেয় যে বাস্তবে কখনো শেষ হয় না। সংখ্যাগুলো পরিসংখ্যানের নানা পরীক্ষায় সমানভাবে ছড়ানো দেখায়, আর অ্যালগরিদমটি দ্রুত। এই কারণে এটি বহু ভাষা আর সফটওয়্যারে ডিফল্ট হয়ে ওঠে; Python-এর random module-ও Python 2.3 থেকে এটি ব্যবহার করে।

সমস্যা হলো, “পরিসংখ্যানে এলোমেলো” আর “অনুমান করা অসম্ভব” এক জিনিস নয়। MT-এর ভেতরের অবস্থা (state) হলো ৬২৪টি ৩২-bit সংখ্যা, আর প্রতিটি আউটপুট এই অবস্থা থেকে একটি উল্টানো যায় এমন (invertible) সূত্রে আসে। ফলে কেউ যদি পরপর ৬২৪টি ৩২-bit আউটপুট দেখতে পায়, সে পুরো ভেতরের অবস্থা গণিত দিয়ে পুনর্গঠন করতে পারে — আর তারপর অতীত ও ভবিষ্যতের প্রতিটি “এলোমেলো” সংখ্যা জেনে যায়। এটি তাত্ত্বিক নয়; অনলাইন গেম, লটারি আর ওয়েব অ্যাপে এমন আক্রমণের বাস্তব উদাহরণ নিরাপত্তা গবেষকরা বহুবার দেখিয়েছেন। ভাবুন, একটি সাইট password reset token বানায় random দিয়ে, আর আক্রমণকারী নিজের অ্যাকাউন্টে বারবার reset চেয়ে যথেষ্ট token দেখে নেয় — এরপর অন্যের token আগাম বলে দেওয়া সম্ভব।

Python-এর ডকুমেন্টেশনে বহু বছর ধরে সতর্কবার্তা ছিল যে random নিরাপত্তার কাজে নয়। তবু মানুষ ব্যবহার করত, কারণ এটি সহজ আর হাতের কাছে; নিরাপদ বিকল্প os.urandom() ছিল, কিন্তু কাঁচা bytes দেয়, ব্যবহার কম সুবিধাজনক। ২০১৫ সালে Steven D'Aprano প্রস্তাব করেন PEP 506: একটি নতুন module, যার নামই বলে দেবে এটি কীসের জন্য — secrets. এটি Python 3.6-এ (২০১৬) যোগ হয়। এর ফাংশনগুলো ঠিক সেই কাজের জন্য তৈরি যা মানুষ ভুল করে random দিয়ে করত: token_hex, token_urlsafe, choice, randbelow, আর সময়-নিরপেক্ষ তুলনার compare_digest. ভেতরে এটি অপারেটিং সিস্টেমের ক্রিপ্টোগ্রাফিক উৎস ব্যবহার করে।

এখানে একটি নকশা-শিক্ষা আছে: সঠিক কাজটি যদি সহজ আর স্পষ্ট নামের না হয়, মানুষ ভুল কাজটিই করবে। ডকুমেন্টেশনের সতর্কবার্তা যথেষ্ট ছিল না; একটি ভালো নামের module সমস্যাটি অনেকখানি মিটিয়েছে। seed-এর পুনরাবৃত্তিযোগ্যতা যেটা টেস্টে আশীর্বাদ, নিরাপত্তায় সেটাই অভিশাপ।

মূল উপলব্ধি: “এলোমেলো দেখায়” আর “অনুমান করা যায় না” আলাদা গুণ। খেলা আর সিমুলেশনে প্রথমটি যথেষ্ট; যেখানে কেউ অনুমান করে লাভবান হতে পারে, সেখানে দ্বিতীয়টি লাগে — আর Python-এ তার নাম secrets.

প্র ০৩ZoneInfo("Asia/Dhaka") কীভাবে জানে বাংলাদেশের সময় UTC+6, আর ২০০৯ সালে কয়েক মাস UTC+7 ছিল? এই তথ্যের উৎস কে রক্ষা করে, আর Python 3.9-এর আগে মানুষ কী করত?

time zone-এর নিয়ম প্রকৃতির নিয়ম নয় — এগুলো সরকারের সিদ্ধান্ত। কোনো দেশ হঠাৎ daylight saving চালু বা বাতিল করে, কেউ পুরো time zone বদলে ফেলে, কখনো মাত্র কয়েক সপ্তাহের নোটিশে। একটি প্রোগ্রাম অতীতের যেকোনো মুহূর্তের সঠিক স্থানীয় সময় জানতে চাইলে তাকে পৃথিবীর প্রতিটি অঞ্চলের পুরো ইতিহাস জানতে হবে। এই ইতিহাস যে ডেটাবেসে রাখা, তার নাম tz database (IANA time zone database), যাকে অনেকে প্রতিষ্ঠাতার নামে “Olson database”-ও বলেন।

১৯৮০-র দশকে Arthur David Olson এটি শুরু করেন, আর বহু বছর ধরে Paul Eggert-সহ স্বেচ্ছাসেবীরা এটি রক্ষণাবেক্ষণ করে আসছেন। ২০১১ সালে একটি কপিরাইট মামলার পর ডেটাবেসটির দায়িত্ব নেয় IANA — সেই প্রতিষ্ঠান, যা ইন্টারনেটের ডোমেইন আর IP ঠিকানার মূল ব্যবস্থাও দেখে। ডেটাবেসটি বছরে কয়েকবার হালনাগাদ হয়, প্রতিবার কোনো না কোনো দেশের নিয়ম বদলের জন্য। "Asia/Dhaka"-এর মতো নামগুলো এখান থেকেই এসেছে — মহাদেশ/প্রধান শহর, দেশের নাম নয়, কারণ দেশের নাম আর সীমানা বদলায়, শহর সাধারণত থাকে। এই ডেটাবেসেই লেখা আছে বাংলাদেশের ২০০৯ সালের সেই অল্প কয়েক মাসের daylight saving.

Python 3.9-এর আগে standard library-তে এই ডেটাবেস পড়ার কোনো ব্যবস্থা ছিল না। মানুষ ব্যবহার করত third-party pytz package, যার ব্যবহারপদ্ধতি ছিল বিভ্রান্তিকর — tzinfo= দিয়ে সরাসরি দিলে ভুল offset আসত, localize() নামের একটি আলাদা ধাপ লাগত, আর এই ভুল অসংখ্য কোডে ঢুকে গিয়েছিল। Paul Ganssle প্রস্তাব করেন PEP 615, যার ফলে Python 3.9-এ (২০২০) এল zoneinfo — যা datetime-এর স্বাভাবিক নিয়মেই কাজ করে।

একটি বুদ্ধিদীপ্ত নকশা-সিদ্ধান্ত: zoneinfo নিজের ভেতরে ডেটাবেস রাখে না, প্রথমে অপারেটিং সিস্টেমেরটি ব্যবহার করে, কারণ সেটি অপারেটিং সিস্টেমের হালনাগাদের সাথেই নতুন থাকে। যেখানে সেটি নেই (যেমন Windows), সেখানে PyPI-এর tzdata package বিকল্প উৎস। Python-এর হালনাগাদ বছরে একবার; সরকার নিয়ম বদলায় যখন খুশি — তাই ডেটা আর কোড আলাদা রাখাই ঠিক।

মূল উপলব্ধি: সময় শুধু গণিত নয়, রাজনীতি আর ইতিহাসও। নিজে “+6 ঘণ্টা” যোগ করে time zone সামলানো কাজ করে যতদিন নিয়ম না বদলায়; একটি রক্ষণাবেক্ষণ করা ডেটাবেস ব্যবহার করলে অতীত আর ভবিষ্যৎ দুটোই ঠিক থাকে।

প্র ০৪SQLite-কে প্রায়ই বলা হয় পৃথিবীর সবচেয়ে বেশি ব্যবহৃত database. একটি সার্ভারবিহীন, এক-ফাইলের database কীভাবে এত বিস্তৃত হলো, আর Python কেন এটিকে standard library-তে রাখল?

বেশিরভাগ database — MySQL, PostgreSQL, Oracle — আলাদা সার্ভার প্রোগ্রাম হিসেবে চলে। আপনার প্রোগ্রাম নেটওয়ার্কের মাধ্যমে তাদের সাথে কথা বলে; সেগুলো ইনস্টল, কনফিগার আর চালু রাখতে হয়। ২০০০ সালে D. Richard Hipp একটি ভিন্ন ধারণা নিয়ে আসেন: database engine-টি হবে একটি ছোট লাইব্রেরি, যা আপনার প্রোগ্রামের ভেতরেই চলবে, আর পুরো database থাকবে একটি সাধারণ ফাইলে। কোনো সার্ভার নেই, কোনো কনফিগারেশন নেই, কোনো প্রশাসক নেই। প্রচলিত গল্প অনুযায়ী, যুক্তরাষ্ট্রের নৌবাহিনীর একটি জাহাজের সফটওয়্যারে কাজ করার সময় তিনি এমন একটি database-এর প্রয়োজন অনুভব করেন, যা সার্ভার বন্ধ থাকলেও কাজ করবে।

এই নকশা অবিশ্বাস্যভাবে সফল হলো। প্রতিটি Android আর iPhone-এ SQLite আছে — আপনার কন্টাক্ট, মেসেজ, অনেক অ্যাপের ডেটা এতেই। প্রধান ওয়েব ব্রাউজারগুলো ইতিহাস আর বুকমার্ক রাখতে এটি ব্যবহার করে, অনেক ডেস্কটপ সফটওয়্যার নিজের ফাইল-ফরম্যাট হিসেবে এটি ব্যবহার করে। sqlite.org নিজেই দাবি করে যে এটি বিশ্বের সবচেয়ে বেশি ব্যবহৃত database engine — কারণ সার্ভার database যেখানে একটি প্রতিষ্ঠানে কয়েকটি চলে, সেখানে SQLite চলে প্রতিটি ডিভাইসে, একেকটিতে অনেকগুলো। আরেকটি বিশেষত্ব: SQLite-এর কোড public domain — কোনো লাইসেন্সের শর্ত ছাড়াই যে কেউ যেকোনো কাজে ব্যবহার করতে পারে। এর নির্ভরযোগ্যতার পেছনে আছে বিপুল পরিমাণ স্বয়ংক্রিয় টেস্ট, যা মূল কোডের চেয়েও বহুগুণ বড়।

Python-এ sqlite3 module আসে Python 2.5-এ (২০০৬), Gerhard Häring-এর তৈরি pysqlite প্রজেক্ট থেকে। এটি “batteries included” দর্শনের নিখুঁত উদাহরণ: শেখার জন্য সত্যিকারের SQL database, কোনো ইনস্টল ছাড়া; ছোট টুলের জন্য নির্ভরযোগ্য সংরক্ষণ — JSON ফাইলের চেয়ে নিরাপদ, কারণ লেখার মাঝপথে বিদ্যুৎ চলে গেলেও (load-shedding!) transaction-এর কারণে database অর্ধেক-লেখা অবস্থায় নষ্ট হয় না।

তবে এর সীমাও আছে। একই সময়ে লিখতে পারে মূলত একজন — তাই হাজার জন একসাথে অর্ডার দেয় এমন বড় ই-কমার্স সাইটে সার্ভার database ভালো। SQLite-এর নিজের পরামর্শও এমন: এটি “client/server database-এর প্রতিদ্বন্দ্বী নয়, fopen()-এর প্রতিদ্বন্দ্বী” — অর্থাৎ নিজের ফাইল-ফরম্যাট বানানোর বদলে এটি ব্যবহার করুন।

মূল উপলব্ধি: কখনো কখনো সবচেয়ে বড় উদ্ভাবন হলো জটিলতা বাদ দেওয়া। সার্ভার, নেটওয়ার্ক আর প্রশাসন সরিয়ে দিয়ে SQLite database-কে একটি সাধারণ ফাইলের মতো সহজ করেছে — আর তাই সে সবখানে পৌঁছেছে।

প্র ০৫deque দুই প্রান্তেই O(1)-এ যোগ-বিয়োগ করে, অথচ list পারে না। ভেতরে deque কীভাবে বানানো? তাহলে মাঝখানের index পড়ায় deque কেন ধীর?

শুরু করি list দিয়ে। পর্ব ১৪-এ দেখেছেন, CPython-এর list ভেতরে একটি dynamic array — মেমরিতে পাশাপাশি রাখা object-ঠিকানার একটি সারি। এতে lst[i] তাৎক্ষণিক, কারণ i নম্বর ঘরের ঠিকানা সরাসরি হিসাব করা যায়। শেষে যোগ করাও গড়ে O(1). কিন্তু শুরুতে যোগ বা শুরু থেকে সরানো মানে বাকি সব ঘরকে এক ঘর সরানো — দশ লাখ জিনিসের list-এ দশ লাখ সরানো। queue হিসেবে list ব্যবহার করলে প্রতিটি pop(0)-এ এই খরচ।

উল্টো চরমে আছে linked list: প্রতিটি জিনিস একটি আলাদা “নোড”-এ, যে আগের আর পরের নোডের ঠিকানা রাখে। শুরু বা শেষে যোগ-বিয়োগ O(1) — শুধু দুটি ঠিকানা বদলানো। কিন্তু প্রতিটি জিনিসের জন্য দুটি বাড়তি ঠিকানা মানে মেমরির বড় অপচয়, আর মেমরিতে ছড়িয়ে থাকা নোডগুলো প্রসেসরের cache ভালোভাবে ব্যবহার করতে পারে না।

CPython-এর deque (Raymond Hettinger-এর লেখা, Python 2.4-এ যোগ হয়) দুইয়ের মাঝামাঝি একটি চতুর নকশা: block-এর doubly linked list. প্রতিটি block একটি ছোট array, যাতে ৬৪টি জিনিসের ঠিকানা ধরে; block-গুলো আগে-পরে linked. deque মনে রাখে বাঁ দিকের block-এ প্রথম জিনিস কোথায় আর ডান দিকের block-এ শেষটি কোথায়। ডানে যোগ করলে শেষ block-এর পরের ঘরে বসে; block ভরে গেলে নতুন একটি block জোড়া হয়। বাঁয়েও ঠিক একই, উল্টো দিকে। কাউকে কখনো সরাতে হয় না — তাই দুই প্রান্তেই O(1). আর প্রতিটি জিনিসের জন্য আলাদা নোড না থাকায় মেমরির অপচয় সাধারণ linked list-এর চেয়ে অনেক কম।

দামটা দিতে হয় মাঝখানে। dq[i] পড়তে deque-কে হিসাব করতে হয় কোন block-এ সেটি আছে, আর সেখানে পৌঁছাতে প্রান্ত থেকে block ধরে ধরে হাঁটতে হয় — মাঝখানের দিকে O(n). (CPython কাছের প্রান্ত থেকে হাঁটে, তাই দুই প্রান্তের কাছের index দ্রুত।) এ কারণেই deque-এ slicing নেই, আর random access-প্রধান কাজে list-ই সেরা। ডকুমেন্টেশন আরও বলে, deque-এর append আর pop thread-safe — একাধিক thread থেকে একসাথে ব্যবহারে এর ভেতরের কাঠামো নষ্ট হয় না (পর্ব ৪১)।

মূল উপলব্ধি: প্রতিটি ডেটা স্ট্রাকচার কিছু কাজ দ্রুত করে অন্য কিছুর দামে। list দেয় তাৎক্ষণিক index, deque দেয় দুই প্রান্তের গতি। কোন কাজ আপনার প্রোগ্রামে সবচেয়ে বেশি হয়, তা জানলেই সঠিক কাঠামো বাছা যায়।

অনুশীলন

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

  1. হাতে চালান: চালানোর আগে লিখুন কী ছাপা হবে — (ক) print(date(2028, 2, 28) + timedelta(days=1)); (খ) dq = deque([1, 2, 3], maxlen=3), তারপর dq.appendleft(0) আর print(list(dq)); (গ) print(Counter("banana").most_common(2)); (ঘ) print(datetime.strptime("07/03/2026", "%d/%m/%Y").month).

    (ক) 2028-02-29 — ২০২৮ লিপ ইয়ার, তাই ফেব্রুয়ারির ২৯ আছে। (খ) [0, 1, 2] — বাঁয়ে যোগ করলে পূর্ণ deque ডান প্রান্ত থেকে ফেলে দেয়, তাই 3 বাদ। (গ) [('a', 3), ('n', 2)]. (ঘ) 3 — দিন/মাস/বছর ধাঁচে মাঝের 03 হলো মাস (মার্চ)। %m/%d/%Y দিলে উত্তর হতো 7.

  2. কোন module? প্রতিটি কাজের জন্য standard library-র একটি module বলুন: (ক) একটি ফোল্ডার zip করে ব্যাকআপ; (খ) ১০০ জন আবেদনকারীর মধ্যে পরীক্ষার হলে বসানোর ক্রম এলোমেলো করা, কিন্তু ফল পরে যাচাই করা যাবে এমনভাবে; (গ) পাসওয়ার্ড ভুলে গেলে পাঠানো reset লিংকের token; (ঘ) একটি অ্যাপের সার্ভারে প্রতিটি লগইনের সময় সংরক্ষণ; (ঙ) ৫০০ পণ্যের মধ্যে সবচেয়ে দামি ১০টি।

    (ক) shutil.make_archive. (খ) random — একটি seed দিয়ে shuffle, আর seed-টি প্রকাশ বা সংরক্ষণ করা, যাতে পরে যে কেউ একই ক্রম পুনরায় তৈরি করে যাচাই করতে পারেন। এখানে লক্ষ্য ন্যায্যতা আর স্বচ্ছতা, গোপনীয়তা নয়। (গ) secrets.token_urlsafe — অনুমান করা গেলে অন্যের অ্যাকাউন্ট দখল হবে। (ঘ) datetime + zoneinfo — aware, UTC-তে সংরক্ষণ, দেখানোর সময় Asia/Dhaka. (ঙ) heapq.nlargest(10, ...) (বা ছোট ডেটায় sorted).

  3. বাগ খুঁজুন: একটি AI এই কোড দিয়েছে: otp = random.randint(1000, 9999), expires = datetime.now() + timedelta(minutes=5), আর পরে যাচাইয়ের সময় if datetime.now(timezone.utc) > expires:. কয়টি সমস্যা দেখছেন?

    তিনটি। (১) OTP-তে random — অনুমানযোগ্য; secrets.randbelow(10**6) আর :06d দিয়ে ৬ অঙ্ক। ৪ অঙ্কে মাত্র ১০,০০০ সম্ভাবনা, তাই চেষ্টার সংখ্যাও সীমিত করা উচিত। (২) expires naive (time zone নেই), কিন্তু তুলনা হচ্ছে aware সময়ের সাথে — চালালে TypeError: can't compare offset-naive and offset-aware datetimes. (৩) naive datetime.now() সার্ভারের স্থানীয় ঘড়ির ওপর নির্ভর করে; দুই জায়গাতেই datetime.now(timezone.utc) ব্যবহার করুন।

  4. ছোট কোড লিখুন: একটি function days_until(text, today) লিখুন, যা "16/12/2026"-এর মতো দিন/মাস/বছর লেখা আর একটি date নিয়ে বাকি দিনের সংখ্যা দেয়, আর তারিখ পেরিয়ে গেলে 0. তারপর একটি দোকানের sales list থেকে সবচেয়ে বেশি বিক্রি হওয়া ৩টি জিনিস বের করুন।

    “আজ” argument হিসেবে আসায় ফাংশনটি স্থির তারিখ দিয়ে টেস্ট করা যায়:

    from collections import Counter
    from datetime import date, datetime
    
    def days_until(text: str, today: date) -> int:
        target = datetime.strptime(text, "%d/%m/%Y").date()
        return max((target - today).days, 0)
    
    print(days_until("16/12/2026", date(2026, 10, 17)))   # 60
    print(days_until("01/01/2026", date(2026, 10, 17)))   # 0
    sales = ["চা", "কলা", "চা", "পানি", "কলা", "চা"]
    print(Counter(sales).most_common(3))   # [('চা', 3), ('কলা', 2), ('পানি', 1)]

    .date() datetime থেকে শুধু তারিখ অংশ নেয়, যাতে date-এর সাথে বিয়োগ করা যায় — datetime আর date সরাসরি বিয়োগ করলে TypeError. max(..., 0) ঋণাত্মক দিন আটকায়।

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

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

ক্রমPython-এর sqlite3 দিয়ে একটি টেবিল বানিয়ে তা থেকে উত্তর পাওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔌 sqlite3.connect() দিয়ে database খোলা
  2. 🏗️ CREATE TABLE দিয়ে টেবিলের কলাম ঠিক করা
  3. 📥 ? চিহ্নসহ INSERT দিয়ে সারি ঢোকানো
  4. 🔍 SELECT ... WHERE দিয়ে প্রশ্ন করা
  5. 📋 fetchall() দিয়ে ফল list হিসেবে নেওয়া, তারপর close()

Python-এ ব্যাংকিং অ্যাপের লগইনের জন্য ৬ অঙ্কের OTP বানাতে কোনটি সবচেয়ে উপযুক্ত?

  • random.randint(100000, 999999)
  • secrets.randbelow(10**6)
  • random.seed(42) তারপর random.randint(...)
  • str(uuid.uuid4())[-6:]

Python-এ dq = deque(maxlen=3) নিয়ে 1, 2, 3, 4, 5 পরপর append করলে list(dq)-এর মান কী?

  • [1, 2, 3, 4, 5]
  • [1, 2, 3]
  • [3, 4, 5]
  • IndexError

Python-এ date(2026, 1, 31) + timedelta(days=1)-এর ফল কী?

  • 2026-02-01
  • 2026-01-32
  • TypeError
  • 2026-02-28

Python-এ "25/12/2026" লেখাটিকে datetime বানাতে datetime.strptime-এ কোন format দিতে হবে?

  • "%d/%M/%Y"
  • "%m/%d/%Y"
  • "%d/%m/%y"
  • "%d/%m/%Y"

Python-এর sqlite3.connect(":memory:") লিখলে কী ঘটে?

  • বর্তমান ফোল্ডারে :memory: নামে একটি ফাইল তৈরি হয়
  • RAM-এ একটি অস্থায়ী database তৈরি হয়, connection বন্ধ হলে মুছে যায়
  • একটি SQLite সার্ভারের সাথে নেটওয়ার্কে যুক্ত হয়
  • ValueError — database-এর জন্য একটি ফাইলের নাম লাগে

Python-এ APP_MODE নামের environment variable সেট করা না থাকলে os.environ.get("APP_MODE", "development")-এর মান কী?

  • KeyError
  • None
  • "development"
  • "" (খালি string)

শব্দকোষ

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

মনে রাখুন

  1. নিজে লেখার আগে খুঁজুন: Python-এর standard library দুই শতাধিক পরীক্ষিত module-এর ভান্ডার; মুখস্থ নয়, মানচিত্র মনে রাখুন আর বিস্তারিত দেখুন docs.python.org-এ।
  2. date − date = timedelta; তারিখ + timedelta = নতুন তারিখ। strptime পড়ে, strftime লেখে (%m মাস, %M মিনিট); বিনিময়ে ISO 8601. “আজ” argument হিসেবে নিন।
  3. naive সময়ের time zone নেই; সার্ভার বা একাধিক দেশে aware সময় — ZoneInfo("Asia/Dhaka"), ভেতরে UTC. সময় মাপতে time.perf_counter().
  4. random খেলা, সিমুলেশন আর seed দিয়ে পুনরাবৃত্তিযোগ্য টেস্টের জন্য; OTP, password, token-এ সবসময় secrets.
  5. গণনায় Counter, “শেষ N টি” আর queue-তে deque, top-k-তে heapq, সাজানো ঢোকানোয় bisect; গোপন সেটিং os.environ-এ; ছোট database-এ sqlite3 আর সবসময় ? দিয়ে মান।

পরের পর্বে: একটি লেখা থেকে সব মোবাইল নম্বর, ইমেইল বা তারিখ খুঁজে বের করতে চান — কিন্তু সেগুলো দেখতে একেকটা একেক রকম? তখন লাগে regular expression, standard library-র re module — প্যাটার্নের একটি ছোট ভাষা, যা দিয়ে “০১ দিয়ে শুরু, তারপর ৯টি অঙ্ক” ধরনের নিয়ম লিখে খোঁজা যায়।

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

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