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

লিখিত ও মৌখিক (ভাইভা) প্রশ্ন — Python মডেল উত্তর ও কৌশল

Written & Viva Questions in Python — Model Answers and How to Present Them

Taught in Bangla, this page prepares you for written exams and viva: what examiners look for, answer structures for short and long questions, how to speak in a viva, model answers to 29 long questions graded by difficulty across the course, 74 rapid-fire viva questions with follow-up chains, and practising with an AI viva board.

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

  • লিখিত উত্তর কোন ক্রমে সাজালে পরীক্ষক সহজে নম্বর দেন — সংজ্ঞা থেকে উপসংহার
  • ছোট প্রশ্ন আর বড় প্রশ্নের উত্তরের দৈর্ঘ্য ও গভীরতার পার্থক্য, আর খাতায় Python কোড লেখার নিয়ম
  • ভাইভায় আগে ছোট নির্ভুল উত্তর, তারপর উদাহরণ — আর “জানি না” সৎভাবে সামলানো
  • পুরো Python কোর্সের ২৯টি ক্লাসিক বড় প্রশ্ন আর ৭৪টি ভাইভা প্রশ্নের মডেল উত্তর
  • AI-কে ভাইভা সঙ্গী হিসেবে ব্যবহার করা — আর তার ভুল ধরা

১ · লিখিত পরীক্ষা ও ভাইভা — পরীক্ষক আসলে কী দেখেন

আগের দুই পর্বে আপনি কোড পড়ে আউটপুট বলেছেন আর MCQ অনুশীলন করেছেন। সেখানে উত্তর হয় ঠিক, নয় ভুল। লিখিত পরীক্ষা আর ভাইভা আলাদা। এখানে দুজন একই তথ্য জানলেও একজন বেশি নম্বর পান — কারণ তিনি তথ্যটি গুছিয়ে, প্রমাণসহ আর নিজের ভাষায় উপস্থাপন করেছেন।

Python-এর লিখিত প্রশ্ন সাধারণত তিন ধরনের হয়। “কী ও কেন” — যেমন “generator কী? কেন দরকার?” “পার্থক্য লিখুন” — যেমন “list ও tuple-এর পার্থক্য।” আর “উদাহরণসহ ব্যাখ্যা করুন” — যেমন “decorator কীভাবে কাজ করে? উদাহরণসহ লিখুন।” Python সহজ ভাষা বলে পরীক্ষকেরা প্রায়ই সহজ-দেখতে প্রশ্নের ভেতরের কারণটা জানতে চান: কেন default argument-এ খালি list দেওয়া বিপজ্জনক, কেন list dict-এর key হতে পারে না, কেন thread দিয়ে CPU-ভারী কাজ সাধারণত দ্রুত হয় না।

যেকোনো লিখিত উত্তর পড়ার সময় একজন পরীক্ষক মনে মনে চারটি জিনিস খোঁজেন:

নির্ভুল সংজ্ঞা

প্রথম এক–দুই বাক্যে মূল কথা। ভুল বা অস্পষ্ট সংজ্ঞা দিয়ে শুরু হলে বাকি উত্তর পড়ার আগেই নম্বর কমে।

শুরু

বোঝার প্রমাণ

“কীভাবে কাজ করে” আর “কেন এভাবে বানানো” — মুখস্থ সংজ্ঞার বাইরে যা বলবেন, সেটাই বোঝার প্রমাণ।

ব্যাখ্যা

চলবে এমন কোড

ছোট, সম্পূর্ণ, ঠিক indentation-এর উদাহরণ — সাথে আউটপুট। আধুনিক Python (f-string, with, pathlib) লিখলে আরও ভালো।

উদাহরণ

সীমা ও ঝুঁকি

কোথায় ব্যবহার করা উচিত নয়, কোন ফাঁদে মানুষ পড়ে, কোনটি CPython-এর বাস্তবায়ন আর কোনটি ভাষার নিয়ম — এই অংশটি ভালো উত্তরকে সেরা উত্তরে নেয়।

উপসংহার

এই পাতার প্রশ্নগুলো কোর্সের মডিউল অনুযায়ী সাজানো। কোনো অংশে বারবার আটকালে টেবিলের পর্বগুলো আবার পড়ুন:

কোর্সের মডিউলপর্বলিখিত প্রশ্নভাইভা প্রশ্ন
১–৩ · ভিত্তি, মূল ভাষা, ডেটা স্ট্রাকচার০১–২১অংশ ৪ (ক-০১–ক-০৯)ভ-০১–ভ-৩২
৪ · বড় প্রোগ্রাম গোছানো২২–২৮অংশ ৫ (খ-০১–খ-০৫)ভ-৩৩–ভ-৪৪
৫ · অবজেক্ট-ওরিয়েন্টেড Python২৯–৩৫অংশ ৬ (গ-০১–গ-০৮)ভ-৪৫–ভ-৫৯
৬–৭ · আধুনিক Python ও বাস্তব প্রয়োগ৩৬–৫১অংশ ৭ (ঘ-০১–ঘ-০৭)ভ-৬০–ভ-৭৪

এই পাতার কোনো প্রশ্ন কোনো নির্দিষ্ট পরীক্ষার প্রশ্নপত্র থেকে নেওয়া নয়। এগুলো এমন ধরনের প্রশ্ন, যা বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় প্রায়ই আসে। সিলেবাস, নম্বর বণ্টন আর সময় সবসময় নিজের পরীক্ষার সর্বশেষ বিজ্ঞপ্তি থেকে মিলিয়ে নিন। সব কোড ও আউটপুট CPython 3.14-এ চালিয়ে যাচাই করা; কোনো আচরণ CPython-এর নিজস্ব বাস্তবায়ন হলে উত্তরে সেটা আলাদা করে বলা আছে।

C/C++ জানলে

উত্তর সাজানোর কাঠামো C আর C++-এর মতোই — সংজ্ঞা, ব্যাখ্যা, কোড, চিত্র, উপসংহার। পার্থক্য প্রশ্নের ধরনে: C-তে যেখানে pointer আর malloc নিয়ে প্রশ্ন আসে, Python-এ সেখানে আসে নাম আর object-এর সম্পর্ক, mutability আর copy. C++-এর “virtual function কীভাবে কাজ করে” প্রশ্নের জায়গায় Python-এ আসে “MRO আর super() কীভাবে কাজ করে।” তুলনার জন্য C-এর লিখিত ও মৌখিক প্রশ্ন আর C++-এর লিখিত ও মৌখিক প্রশ্ন পর্ব দুটি দেখতে পারেন।

২ · উত্তরের কাঠামো — ছোট প্রশ্ন আর বড় প্রশ্ন

প্রায় যেকোনো “ব্যাখ্যা করুন” প্রশ্নের জন্য একটি পাঁচ অংশের কাঠামো কাজ করে। একে একটি দালানের মতো ভাবুন — সংজ্ঞা হলো ভিত, ব্যাখ্যা দেয়াল, কোড আর চিত্র জানালা-দরজা, আর উপসংহার ছাদ। ভিত না থাকলে বাকিটা দাঁড়ায় না, ছাদ না থাকলে দালান অসম্পূর্ণ।

সংজ্ঞা → ব্যাখ্যা → উদাহরণ কোড → চিত্র / টেবিল → উপসংহার

ছোট প্রশ্ন বনাম বড় প্রশ্ন

প্রশ্নের নম্বর আর সময় দেখে কাঠামো ছোট-বড় করুন। একটি সাধারণ হিসাব: মোট মিনিটকে মোট নম্বর দিয়ে ভাগ করলে পাবেন প্রতি নম্বরে কত মিনিট — সেই হিসাবে প্রতিটি প্রশ্নে সময় দিন।

বিষয়ছোট প্রশ্নবড় প্রশ্ন
দৈর্ঘ্য৫–১০ লাইন, অর্ধেক পাতার মতো১–২ পাতা
অংশসংজ্ঞা + এক লাইনের ব্যাখ্যা + ছোট উদাহরণ বা একটি পয়েন্টপাঁচটি অংশই — সংজ্ঞা, ব্যাখ্যা, কোড ও আউটপুট, চিত্র বা টেবিল, উপসংহার
কোড২–৪ লাইনের টুকরো যথেষ্ট১০–২০ লাইনের সম্পূর্ণ উদাহরণ, যা আলাদা ফাইলে চালালেই চলবে
“পার্থক্য লিখুন” হলে৩–৪ সারির টেবিল৫–৭ সারির টেবিল + দুই পাশের ছোট উদাহরণ + কোনটি কখন বেছে নেবেন
সাধারণ ভুলঅপ্রয়োজনীয় ইতিহাস লিখে সময় নষ্টশুধু কোড, কোনো ব্যাখ্যা নেই — অথবা শুধু লেখা, কোনো কোড নেই

এবার একটি ক্লাসিক বড় প্রশ্ন দিয়ে কাঠামোটি হাতে-কলমে দেখি: “Python-এ decorator কীভাবে কাজ করে? উদাহরণসহ লিখুন।” নিচের ডায়াগ্রামে “পরে” চাপুন — প্রতিটি ধাপে উত্তরপত্রে একটি নতুন অংশ যোগ হবে।

  1. সংজ্ঞাএক-দুই বাক্যে
  2. ব্যাখ্যা@ মানে কী
  3. কোডআউটপুটসহ
  4. চিত্রনাম কোথায় দেখায়
  5. উপসংহারব্যবহার ও সতর্কতা
উত্তরপত্রধাপ ১ — সংজ্ঞা
Decorator হলো এমন একটি callable (সাধারণত ফাংশন), যা
আরেকটি ফাংশন নিয়ে তার আগে-পরে নতুন কাজ জুড়ে দেওয়া
একটি নতুন ফাংশন ফেরত দেয় — মূল ফাংশনের কোড না বদলে।
উত্তরপত্রধাপ ২ — ব্যাখ্যা যোগ হলো
… (সংজ্ঞা) …

ভিত্তি: Python-এ ফাংশন first-class object — ভ্যারিয়েবলে
  রাখা যায়, argument হিসেবে পাঠানো যায়, ফেরত দেওয়া যায়।
@log লিখে def pay(...) মানে হুবহু:  pay = log(pay)
  এটি ঘটে def চলার মুহূর্তে (module লোডের সময়), ডাকার সময় নয়।
ভেতরের wrapper একটি closure — মূল func-কে মনে রাখে।
wrapper(*args, **kwargs) সব argument নিয়ে func-এ পাঠায়,
  আর func-এর ফল return করে।
উত্তরপত্রধাপ ৩ — উদাহরণ কোড
… (সংজ্ঞা, ব্যাখ্যা) …

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("ডাকা হলো:", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log
def pay(amount):
    return amount - 10

print(pay(500))
আউটপুট:  ডাকা হলো: pay
         490
উত্তরপত্রধাপ ৪ — চিত্র
… (সংজ্ঞা, ব্যাখ্যা, কোড) …

decorate-এর আগে:   pay ──────────────► [মূল pay ফাংশন]

decorate-এর পরে:   pay ──► [wrapper] ──func──► [মূল pay]
                              │  (closure-এ রাখা)
pay(500) → wrapper(500) → print → func(500) → 490 ফেরত
নাম "pay" এখন wrapper-কে দেখায়; মূল ফাংশন হারায়নি।
উত্তরপত্রধাপ ৫ — সম্পূর্ণ উত্তর
… (সংজ্ঞা, ব্যাখ্যা, কোড, চিত্র) …

উপসংহার:
• ব্যবহার: লগ, সময় মাপা, অনুমতি যাচাই, retry, cache
  (functools.lru_cache নিজেই একটি decorator).
• functools.wraps না দিলে নাম আর docstring হারায়।
• argument-সহ decorator (@retry(3)) তিন স্তরের।
• কয়েকটি stack করলে নিচেরটি আগে লাগে।
• সতর্কতা: বেশি decorator কোড পড়া ও debug কঠিন করে।
  1. ধাপ ১ — সংজ্ঞা। তিনটি মূল কথা থাকতেই হবে: ফাংশন নেয়, নতুন ফাংশন ফেরত দেয়, আর মূল কোড না বদলে আচরণ যোগ করে. এর একটি বাদ পড়লে সংজ্ঞা অসম্পূর্ণ। দুই-তিন বাক্যই যথেষ্ট।
  2. ধাপ ২ — ব্যাখ্যা। first-class function, @ যে শুধু syntactic sugar, আর ভেতরের wrapper যে একটি closure — এই তিনটিই বোঝার প্রমাণ। “decorate হয় def চলার সময়” কথাটি অনেকে জানেন না, তাই উত্তর আলাদা হয়ে যায়।
  3. ধাপ ৩ — উদাহরণ কোড। ছোট, সম্পূর্ণ, চালানো যায় এমন। *args, **kwargs আর functools.wraps কোডেই দেখান। শেষে আউটপুট — পরীক্ষক বোঝেন আপনি মনে মনে প্রোগ্রামটি চালিয়ে দেখেছেন।
  4. ধাপ ৪ — চিত্র। নামটি আগে কোন object-কে দেখাত, পরে কোনটিকে — তীর দিয়ে আঁকুন। Python-এ “নাম একটি লেবেল” ধারণাটি এই চিত্রেই সবচেয়ে পরিষ্কার হয়।
  5. ধাপ ৫ — উপসংহার। বাস্তব ব্যবহার, wraps, decorator factory, stacking-এর ক্রম আর সতর্কতা — পাঁচটি বুলেট। পরীক্ষক এখানে দেখেন আপনি বাস্তব কোডে এটি ব্যবহার করতে জানেন কি না। এখন উত্তরটি সম্পূর্ণ।
একটি সম্ভাব্য সময়-ভাগ (আমাদের পরামর্শ)

বড় প্রশ্নে মোট সময়ের মোটামুটি এক-পঞ্চমাংশ সংজ্ঞা ও ব্যাখ্যায়, অর্ধেকের মতো কোড ও চিত্রে, আর বাকিটা উপসংহার ও শেষবার চোখ বোলাতে দিন। প্রতিটি পরীক্ষকের নম্বর-ভাগ আলাদা হতে পারে, কিন্তু পাঁচটি অংশই থাকলে কোনো ভাগে বড় ফাঁক থাকে না।

খাতায় Python কোড লেখার ছয় নিয়ম

  1. Indentation-ই ব্যাকরণ: Python-এ indentation ভুল মানে কোড ভুল। খাতায় প্রতিটি স্তরে স্পষ্ট চার ঘর সরান; দরকার হলে বাঁ পাশে হালকা খাড়া দাগ টেনে স্তর দেখান।
  2. আধুনিক রূপ: print "x" (Python 2) নয়, print("x"); % ফরম্যাটের বদলে f-string; ফাইল খুলতে with open(..., encoding="utf-8").
  3. PEP 8 নাম: ভ্যারিয়েবল ও ফাংশন snake_case, class PascalCase — a, b নয়, balance, owner.
  4. দরকারি import লিখুন: copy.deepcopy ব্যবহার করলে ওপরে import copy. পরীক্ষক কোডটিকে সম্পূর্ণ দেখতে চান।
  5. আউটপুট লিখুন: কোডের নিচে “আউটপুট: …”. dict বা set ছাপালে মনে রাখুন — dict insertion order রাখে, কিন্তু string-এর set-এর ক্রম নিশ্চিত নয়।
  6. Edge case এক লাইনে: খালি list, শূন্য দিয়ে ভাগ, ভুল টাইপের ইনপুট — কোডে না রাখলেও নিচে উল্লেখ করুন।

৩ · ভাইভায় কথা বলার কৌশল

ভাইভা অনেকটা ক্রিকেটে DRS রিভিউয়ের মতো — প্রথম সিদ্ধান্তের পর তৃতীয় আম্পায়ার একের পর এক অ্যাঙ্গেল দেখেন। পরীক্ষকও আপনার প্রথম উত্তর শুনে সেখান থেকে পরের প্রশ্ন বানান। এমন প্রশ্নকে বলে follow-up প্রশ্ন. তাই ভাইভার আসল দক্ষতা হলো প্রশ্নের শিকল সামলানো।

  • আগে ছোট নির্ভুল উত্তর, তারপর উদাহরণ: “generator হলো এমন ফাংশন, যা yield দিয়ে একটি একটি করে মান দেয় আর মাঝখানে থেমে থাকে। যেমন বড় লগ ফাইল লাইন-বাই-লাইন পড়া।” পরীক্ষক চাইলে বিস্তারে যান।
  • Think aloud: নিশ্চিত না হলে ভাবনাটা জোরে বলুন — “আমি এভাবে ভাবছি: dict key খোঁজে hash দিয়ে, তাহলে key বদলে গেলে…”. পরীক্ষক আপনার যুক্তির পথ দেখতে চান।
  • সৎ থাকুন: “জানি না, তবে এভাবে ভাবব…” বলা দুর্বলতা নয়। আন্দাজে ভুল তথ্য জোর দিয়ে বললে পরীক্ষক আপনার বাকি ঠিক উত্তরগুলো নিয়েও সন্দেহ করেন।
  • ভাষা আর বাস্তবায়ন আলাদা করুন: “a is b কি True হবে?” — “ছোট int-এর ক্ষেত্রে CPython-এ সাধারণত True, কারণ CPython −৫ থেকে ২৫৬ পর্যন্ত int আগে থেকে বানিয়ে রাখে; কিন্তু এটি ভাষার নিয়ম নয়, তাই মান তুলনায় সবসময় ==.” এই সতর্কতা অভিজ্ঞতার চিহ্ন।
  • সংস্করণ বলুন: “match কবে এসেছে?” — “Python 3.10 থেকে।” “dict কি ক্রম রাখে?” — “Python 3.7 থেকে insertion order ভাষার নিশ্চয়তা।” সংস্করণ জানা না থাকলে বলুন “আধুনিক Python-এ”.
  • বোর্ড চান: জটিল কিছু হলে বলুন “একটু এঁকে দেখাই?” — নাম আর object-এর তীর-চিত্র মুখের কথার চেয়ে অনেক শক্তিশালী।

নিচের ডায়াগ্রামে একটি বাস্তবধর্মী ভাইভার শিকল দেখুন। সহজ “list কী?” থেকে শুরু করে প্রশ্ন একটু একটু করে গভীরে যাচ্ছে। শেষ ধাপে প্রার্থী পুরো নিশ্চিত নন — তবু সঠিকভাবে সামলাচ্ছেন।

প্রশ্ন ১সহজ শুরু
পরীক্ষক: list কী?
আপনি:   ক্রম রাখা, বদলানো যায় এমন একটি sequence —
        যেকোনো টাইপের object রাখা যায়, index দিয়ে পড়া যায়।
        যেমন marks = [80, 75, 90]; marks.append(60).
প্রশ্ন ২ফলো-আপ — ভেতরের কৌশল
পরীক্ষক: list বদলানো যায় কেন? tuple যায় না কেন?
আপনি:   CPython-এ list ভেতরে object-গুলোর reference-এর একটি
        dynamic array; append, insert সেই array বদলায়।
        tuple তৈরির পর আকার ও reference বদলানোর কোনো method
        নেই — নকশা করেই immutable, তাই নিরাপদে ভাগ করা যায়।
প্রশ্ন ৩ফলো-আপ — পরিণতি
পরীক্ষক: তাহলে list কেন dict-এর key হতে পারে না?
আপনি:   dict key-র hash দিয়ে ঘর ঠিক করে। list বদলালে তার
        hash-ও বদলাতে হতো, তখন পুরোনো ঘরে আর খুঁজে পাওয়া
        যেত না। তাই list unhashable — hash(list) দিলে
        TypeError: unhashable type: 'list'. tuple key হতে পারে।
প্রশ্ন ৪ফলো-আপ — ফাঁদ
পরীক্ষক: সব tuple-ই কি key হতে পারে?
আপনি:   না। tuple hashable হয় তখনই, যখন তার ভেতরের সবকিছু
        hashable. ([1, 2], 3) tuple, কিন্তু ভেতরে list আছে —
        তাই এর hash চাইলেও TypeError. immutable মানে শুধু
        tuple-এর reference-গুলো বদলায় না।
প্রশ্ন ৫কঠিন — পুরো নিশ্চিত নন
পরীক্ষক: t = ([1],); t[0] += [2] চালালে কী হবে?
আপনি:   পুরো নিশ্চিত নই, তবে এভাবে ভাবছি — += আগে list-এর
        নিজের in-place যোগ চালায়, তাই list বদলে যাবে। তারপর
        ফলটা আবার t[0]-তে বসাতে চাইবে, আর tuple তা দেবে না।
        তাই TypeError আসবে, কিন্তু list-টা হয়তো বদলে যাবে।
(ঠিক: TypeError: 'tuple' object does not support item
assignment — আর তারপর t দেখলে ([1, 2],).)
  1. সহজ শুরু। সংজ্ঞা সংক্ষেপে, সাথে একটি উদাহরণ। এখানে বেশি বলার দরকার নেই — পরীক্ষক পরের প্রশ্নের জন্য সূত্র খুঁজছেন।
  2. “কেন” প্রশ্ন। dynamic array আর immutable — দুটি ধারণা আর এক লাইনের কারণ। এই উত্তরেই বোঝা যায় আপনি শুধু ব্যবহার নয়, ভেতরের নকশাও জানেন।
  3. পরিণতি। mutability থেকে hashable আর hash table-এ যাওয়া — একটি ধারণা থেকে আরেকটি টেনে আনার ক্ষমতা পরীক্ষক সবচেয়ে বেশি পছন্দ করেন। exception-এর নাম হুবহু বলুন।
  4. প্রচলিত ভুল ধারণা পরীক্ষা। অনেকে বলেন “tuple মানেই hashable” — ভুল। “immutable হলো শুধু reference-গুলো, ভেতরের object নয়” — এটিই shallow immutability.
  5. “জানি না” সামলানো। প্রার্থী সৎভাবে বলছেন নিশ্চিত নন, তারপর +=-কে দুই ধাপে ভেঙে যুক্তি দিচ্ছেন — আর যুক্তিটি আসলে সঠিক। এভাবে ভেবে বলা উত্তর প্রায়ই পূর্ণ বা অন্তত আংশিক কৃতিত্ব পায়।

ভাইভাকে বিকাশে টাকা পাঠানোর মতো ভাবুন। নম্বরের একটি অঙ্ক ভুল হলে টাকা ভুল জায়গায় যায়, তাই পাঠানোর আগে অ্যাপ নাম দেখিয়ে নিশ্চিত হতে বলে। ভাইভাতেও প্রশ্ন অস্পষ্ট হলে আগে নিশ্চিত হয়ে নিন — “আপনি কি CPython-এর কথা বলছেন, নাকি ভাষার নিয়মের?” বা “free-threaded build-এর কথা, নাকি সাধারণ Python-এর?” ভুল প্রশ্নের নিখুঁত উত্তরের চেয়ে সঠিক প্রশ্নের সাধারণ উত্তর অনেক বেশি কাজের।

৪ · অংশ ক — ভিত্তি, মূল ভাষা আর ডেটা স্ট্রাকচার (মডিউল ১–৩)

রিভিশনের জন্য: পর্ব ০১, পর্ব ০৫, পর্ব ০৬, পর্ব ১৪, পর্ব ১৫, পর্ব ১৬, পর্ব ১৭, পর্ব ১৮, পর্ব ১৯, পর্ব ২০.

প্রতিটি প্রশ্ন পড়ে আগে নিজে খাতায় উত্তর লিখুন, তারপর প্রশ্নে চেপে মেলান। মডেল উত্তরগুলো সংক্ষিপ্ত কিন্তু সম্পূর্ণ — বড় প্রশ্নে অংশ ২-এর কাঠামো মেনে চিত্র ও কোড যোগ করুন।

ক-০১Python কেন এত জনপ্রিয়? Python কি interpreted ভাষা? bytecode কী? (সহজ)

সংজ্ঞা: Python একটি উচ্চস্তরের, সাধারণ-উদ্দেশ্যের, dynamically typed ভাষা। Guido van Rossum ১৯৮৯ সালের শেষ দিকে নেদারল্যান্ডসের CWI-তে কাজ শুরু করেন, প্রথম প্রকাশ ১৯৯১ সালে। Python 3.0 আসে ২০০৮ সালে, আর Python 2-এর আনুষ্ঠানিক সমর্থন শেষ হয় ২০২০ সালের ১ জানুয়ারি।

জনপ্রিয়তার কারণ: পড়তে প্রায় ইংরেজির মতো সহজ; বিশাল standard library (“batteries included”); PyPI-তে লাখ লাখ package; আর data science, AI, web, automation, scripting — এক ভাষায় প্রায় সব কাজ। দাম: সাধারণ Python কোড C-এর তুলনায় অনেক ধীর, তাই ভারী হিসাব NumPy-এর মতো C-তে লেখা লাইব্রেরিতে পাঠানো হয়।

Interpreted নাকি compiled? দুটোই আংশিক। সবচেয়ে প্রচলিত বাস্তবায়ন CPython আগে source কোডকে bytecode-এ compile করে — একটি নিচু স্তরের, প্ল্যাটফর্ম-নিরপেক্ষ নির্দেশের তালিকা। তারপর CPython-এর virtual machine সেই bytecode একটি একটি করে চালায় (interpret করে)। import করা module-এর bytecode __pycache__ ফোল্ডারে .pyc ফাইলে জমা থাকে, যাতে পরের বার আবার compile করতে না হয়।

ধাপC (gcc)Python (CPython)
অনুবাদআগেই পুরো প্রোগ্রাম machine code-এচালানোর সময় bytecode-এ
চালায় কেসরাসরি CPUCPython-এর virtual machine
টাইপ যাচাইcompile-এর সময়চলার সময় (dynamic)
গতিদ্রুততুলনায় ধীর; 3.11 থেকে specializing interpreter অনেকটা দ্রুত করেছে

উপসংহার: “Python interpreted” কথাটি মোটামুটি ঠিক, কিন্তু পূর্ণ উত্তর হলো “CPython source-কে bytecode-এ compile করে, তারপর virtual machine-এ interpret করে।” ভাষা আর বাস্তবায়ন আলাদা — PyPy-র মতো বাস্তবায়ন JIT দিয়ে চলার সময় machine code বানায়, আর CPython 3.13 থেকে একটি পরীক্ষামূলক JIT যোগ হয়েছে।

ঝালিয়ে নিন: পর্ব ০১ · Python কেন?, পর্ব ০৫ · Python কীভাবে চলে

ক-০২Dynamic typing কী? “Python-এ ভ্যারিয়েবল হলো নাম, বাক্স নয়” — ব্যাখ্যা করুন। (মাঝারি)

সংজ্ঞা: Dynamic typing মানে টাইপ থাকে object-এর সাথে, নামের সাথে নয়; আর টাইপ যাচাই হয় চলার সময়। একই নাম আগে int, পরে str-কে দেখাতে পারে। Python একই সাথে strongly typed — "5" + 3 নিজে থেকে রূপান্তর করে না, TypeError দেয়।

নাম বনাম বাক্স: C-তে int x = 5; মানে মেমরিতে x নামের একটি বাক্স, যার ভেতরে 5. Python-এ x = 5 মানে: একটি int object 5 আছে, আর নাম x সেটির গায়ে একটি লেবেল (binding). y = x নতুন object বানায় না — একই object-এ দ্বিতীয় লেবেল লাগায় (alias).

a = [1, 2]
b = a            # নতুন list নয় — একই list-এ দ্বিতীয় নাম
b.append(3)
print(a)         # [1, 2, 3]
print(a is b)    # True  — একই object
b = [9]          # b-কে নতুন object-এ বাঁধা হলো; a বদলায়নি
print(a, b)      # [1, 2, 3] [9]
a ──┐
    ├──► [1, 2, 3]        b = [9] করার পর:   a ──► [1, 2, 3]
b ──┘                                       b ──► [9]

উপসংহার: mutation (append) আর rebinding (=) আলাদা — প্রথমটি object বদলায়, দ্বিতীয়টি শুধু নামটিকে অন্য object-এ সরায়। ফাংশনে argument পাঠানোও একই নিয়মে চলে (call by sharing): ফাংশন list বদলালে বাইরেও দেখা যায়, কিন্তু parameter-কে নতুন object-এ বাঁধলে বাইরের নাম বদলায় না। মান তুলনায় ==, একই object কি না দেখতে is.

ঝালিয়ে নিন: পর্ব ০৬ · ভ্যারিয়েবল ও টাইপ, পর্ব ০৮ · is বনাম ==

ক-০৩Mutable আর immutable object-এর পার্থক্য উদাহরণসহ লিখুন। (সহজ)

সংজ্ঞা: Mutable object তৈরির পর নিজের ভেতরের অবস্থা বদলাতে পারে; immutable object পারে না — “বদলানো” মনে হলে আসলে নতুন object তৈরি হয়।

বিষয়MutableImmutable
উদাহরণlist, dict, set, bytearray, সাধারণ class-এর objectint, float, bool, str, tuple, frozenset, bytes
জায়গায় বদলlst.append(4) — একই objects = s + "!" — নতুন str, নাম নতুনটিতে
dict key / set সদস্যহতে পারে না (unhashable)হতে পারে (ভেতরের সবকিছু hashable হলে)
ঝুঁকিalias দিয়ে অজান্তে বদলবারবার জোড়া লাগালে অনেক নতুন object
s = "ঢাকা"
s[0] = "ক"       # TypeError: 'str' object does not support item assignment
t = (1, [2, 3])
t[1].append(4)   # চলে! tuple-এর ভেতরের list mutable
print(t)         # (1, [2, 3, 4])

উপসংহার: immutable object thread-এর মধ্যে নিরাপদে ভাগ করা যায়, hash করা যায়, আর ভুল করে বদলে যাওয়ার ভয় নেই। কিন্তু tuple-এর immutability অগভীর: tuple-এর reference বদলায় না, ভেতরের mutable object বদলাতে পারে। লুপে অনেক string জোড়া লাগাতে +=-এর বদলে "".join(parts) ব্যবহার করুন।

ঝালিয়ে নিন: পর্ব ১৯ · mutability ও copy, পর্ব ০৯ · string

ক-০৪list, tuple, set আর dict-এর পার্থক্য লিখুন — প্রধান কাজগুলোর time complexity-সহ। কখন কোনটি বেছে নেবেন? (মাঝারি)

সংজ্ঞা: চারটিই built-in collection. list — ক্রম রাখা, বদলানো যায়; tuple — ক্রম রাখা, বদলানো যায় না; set — ক্রমহীন, অনন্য, hashable সদস্য; dict — key থেকে value-র ম্যাপ, key অনন্য ও hashable. set আর dict ভেতরে hash table.

বিষয়listtuplesetdict
লেখা[1, 2](1, 2){1, 2}{"a": 1}
বদলানো যায়হ্যাঁনাহ্যাঁহ্যাঁ
ক্রমহ্যাঁহ্যাঁনিশ্চিত নয়insertion order (3.7 থেকে নিশ্চিত)
ডুপ্লিকেটচলেচলেনাkey-তে না
index দিয়ে পড়াO(1)O(1)নেইkey দিয়ে গড়ে O(1)
x in ...O(n)O(n)গড়ে O(1)key-তে গড়ে O(1)
শেষে যোগappend amortized O(1)নেই (নতুন tuple)add গড়ে O(1)d[k] = v গড়ে O(1)
শুরুতে যোগ/মোছাO(n)নেই——

উদাহরণ: ক্লাসের রোল অনুযায়ী নম্বর — list; একটি স্থানাঙ্ক (23.8, 90.4) বা dict-এর যৌগিক key — tuple; কোন কোন জেলা থেকে অর্ডার এসেছে (অনন্য) — set; ফোন নম্বর থেকে গ্রাহকের নাম — dict.

উপসংহার: “অনেকবার in দিয়ে খুঁজব” হলে list নয়, set বা dict — বড় ডেটায় পার্থক্য সেকেন্ড বনাম ঘণ্টা। hash table-এর O(1) গড় হিসাব; অনেক collision হলে worst case O(n). সামনে থেকে বারবার বের করতে list-এর বদলে collections.deque.

ঝালিয়ে নিন: পর্ব ১৪, পর্ব ১৫, পর্ব ১৬, পর্ব ১৭

ক-০৫Shallow copy আর deep copy-র পার্থক্য উদাহরণ ও চিত্রসহ লিখুন। (মাঝারি)

সংজ্ঞা: Shallow copy নতুন একটি বাইরের container বানায়, কিন্তু ভেতরের object-গুলোর reference হুবহু কপি করে — ভেতরের object ভাগাভাগি হয়। Deep copy ভেতরের সবকিছুও পুনরাবৃত্তভাবে (recursively) নতুন করে বানায় — কোনো mutable অংশ ভাগাভাগি থাকে না।

import copy

marks = [[80, 75], [90, 60]]
alias = marks                  # কপিই নয় — একই list
shallow = copy.copy(marks)     # marks.copy() বা marks[:] একই কাজ
deep = copy.deepcopy(marks)

marks[0][0] = 99               # ভেতরের list বদলালাম
marks.append([50, 50])         # বাইরের list বদলালাম

print("alias  :", alias)
print("shallow:", shallow)
print("deep   :", deep)
print(shallow is marks, shallow[0] is marks[0], deep[0] is marks[0])

আউটপুট: alias-এ সব বদল দেখা যায় ([[99, 75], [90, 60], [50, 50]]), কারণ এটি একই object. shallow-এ নতুন যোগ হওয়া [50, 50] নেই (বাইরের list আলাদা), কিন্তু 99 আছে ([[99, 75], [90, 60]]) — কারণ ভেতরের list দুজনের ভাগে। deep পুরো অক্ষত: [[80, 75], [90, 60]]. শেষ লাইন False True False ঠিক এই কথাই প্রমাণ করে।

marks   ──► [ ● , ● ]──┐
shallow ──► [ ● , ● ]──┴──► [99, 75]   [90, 60]   (ভেতরের list ভাগে)
deep    ──► [ ● , ● ]─────► [80, 75]'  [90, 60]'  (নিজস্ব কপি)

উপসংহার: সমতল list (ভেতরে শুধু int, str) হলে shallow copy-ই যথেষ্ট ও দ্রুত। nested mutable ডেটা হলে copy.deepcopy — এটি ধীর, তবে একই object দুবার থাকলে বা চক্র থাকলেও (memo রেখে) ঠিকভাবে কপি করে। ফাঁদ: [[0] * 3] * 3 তিনটি সারি নয়, একই সারির তিনটি reference; সঠিক রূপ [[0] * 3 for _ in range(3)].

ঝালিয়ে নিন: পর্ব ১৯ · mutability, alias আর copy/deepcopy

ক-০৬Python-এ scope-এর LEGB নিয়ম কী? Closure কী, উদাহরণসহ ব্যাখ্যা করুন। (মাঝারি)

সংজ্ঞা: কোনো নাম পড়ার সময় Python চারটি স্তরে ক্রমানুসারে খোঁজে — LEGB: Local (বর্তমান ফাংশন), Enclosing (বাইরের ফাংশন), Global (module), Built-in (len, print). প্রথম যেখানে পায়, সেখানেই থামে। কোনো ফাংশনে একটি নামে assign করলেই সেটি পুরো ফাংশনে local ধরা হয় — এটি compile-এর সময়ই ঠিক হয়।

Closure হলো এমন ভেতরের ফাংশন, যা বাইরের ফাংশনের local ভ্যারিয়েবল মনে রাখে — বাইরের ফাংশন শেষ হয়ে যাওয়ার পরও। বাইরের ভ্যারিয়েবল বদলাতে চাইলে nonlocal লিখতে হয়।

x = "global"

def outer():
    x = "enclosing"
    def inner():
        print("inner দেখে:", x)   # L-এ নেই, E-তে পেল
    inner()

outer()
print(len("abc"))                 # len এসেছে B (built-in) থেকে

def make_counter():
    count = 0
    def step():
        nonlocal count            # না লিখলে UnboundLocalError
        count += 1
        return count
    return step

c1 = make_counter()
c2 = make_counter()
print(c1(), c1(), c1(), c2())
print(c1.__closure__[0].cell_contents)

আউটপুট: inner দেখে: enclosing, 3, 1 2 3 1, 3. c1 আর c2 আলাদা closure — প্রতিটি make_counter() ডাকে নতুন count তৈরি হয়, তাই c2 শুরু করে ১ থেকে। শেষ লাইন দেখায় মানটি ফাংশনের __closure__-এর একটি cell-এ জমা আছে।

উপসংহার: closure দিয়ে class ছাড়াই ছোট অবস্থা (state) রাখা যায়, আর decorator এর ওপরই দাঁড়িয়ে। দুটি ফাঁদ: (১) nonlocal/global ছাড়া বাইরের নামে assign করলে UnboundLocalError; (২) লুপে lambda বানালে সব lambda লুপ ভ্যারিয়েবলের শেষ মান দেখে (late binding) — সমাধান lambda i=i: i.

ঝালিয়ে নিন: পর্ব ২০ · scope, closure আর default-এর ফাঁদ

ক-০৭*args আর **kwargs কী? ফাংশনের parameter-এর ধরনগুলো ক্রমসহ লিখুন। (সহজ)

সংজ্ঞা: *args বাড়তি positional argument-গুলো একটি tuple-এ জড়ো করে; **kwargs বাড়তি keyword argument-গুলো একটি dict-এ জড়ো করে। args/kwargs শুধু প্রচলিত নাম — আসল কাজ করে * আর **.

def order(item, /, qty=1, *extras, note="", **info):
    print(item, qty, extras, note, info)

order("চা", 2, "চিনি", "আদা", note="কম গরম", table=5)
# চা 2 ('চিনি', 'আদা') কম গরম {'table': 5}

nums = [3, 1, 2]
print(max(*nums))            # 3 — ডাকার সময় * list খুলে দেয়
opts = {"sep": "-", "end": "!\n"}
print("a", "b", **opts)      # a-b!

Parameter-এর ক্রম: positional-only (/-এর আগে, Python 3.8 থেকে) → সাধারণ → *args → keyword-only (*-এর পরে) → **kwargs. যেমন def f(a, /, b, *args, c, **kw).

উপসংহার: সংজ্ঞায় */** জড়ো করে (packing), ডাকার সময় খুলে দেয় (unpacking). সবচেয়ে বড় ব্যবহার decorator-এর wrapper আর অন্য ফাংশনে argument হুবহু পাঠানো। তবে সাধারণ API-তে স্পষ্ট parameter নাম পড়তে ও যাচাই করতে সহজ — সবকিছু **kwargs-এ ফেলবেন না।

ঝালিয়ে নিন: পর্ব ১৩ · ফাংশন, পর্ব ২০ · *args/**kwargs

ক-০৮Mutable default argument-এর সমস্যা কী? কেন হয়, সমাধান কী? (মাঝারি)

সংজ্ঞা: default মান হিসাব হয় একবারই — def চলার সময়, প্রতিবার ডাকার সময় নয়। তাই default যদি mutable (list, dict) হয়, সব ডাক সেই একই object ভাগ করে, আর এক ডাকের বদল পরের ডাকে থেকে যায়। একে বলে mutable default-এর ফাঁদ।

def add_item(item, basket=[]):
    basket.append(item)
    return basket

print(add_item("চাল"))
print(add_item("ডাল"))            # নতুন খালি list আশা করেছিলেন?
print(add_item.__defaults__)      # default object ফাংশনের গায়ে জমা

def add_item_safe(item, basket=None):
    if basket is None:
        basket = []               # প্রতিবার নতুন list
    basket.append(item)
    return basket

print(add_item_safe("চাল"))
print(add_item_safe("ডাল"))

আউটপুট: ['চাল'], তারপর ['চাল', 'ডাল'] — দ্বিতীয় ডাকে আগের চাল রয়ে গেছে। __defaults__ দেখায় (['চাল', 'ডাল'],): default list ফাংশন object-এর গায়েই বাস করে। নিরাপদ সংস্করণে প্রতিবার ['চাল'] আর ['ডাল'] — আলাদা list.

উপসংহার: নিয়ম: default-এ mutable নয়, None দিন আর ভেতরে নতুন object বানান। একই কারণে def log(t=datetime.now()) সব ডাকে একই সময় দেয়। ruff-এর মতো linter এই ভুল ধরে। কখনো কখনো ইচ্ছা করে এটি cache হিসেবে ব্যবহার হয়, কিন্তু সেটি পরিষ্কার নয় — functools.lru_cache ভালো।

ঝালিয়ে নিন: পর্ব ২০ · default-এর ফাঁদ, পর্ব ১৩ · default argument

ক-০৯List comprehension আর generator expression কী? দুটির পার্থক্য উদাহরণসহ লিখুন। (সহজ)

সংজ্ঞা: Comprehension হলো একটি iterable থেকে নতুন collection বানানোর সংক্ষিপ্ত রূপ: [expr for x in it if cond]. list, dict ({k: v for ...}) আর set ({x for ...}) — তিন রকম আছে। Generator expression একই রকম দেখতে, কিন্তু গোল বন্ধনীতে — (expr for x in it) — এটি সব মান একসাথে বানায় না, চাইলে একটি একটি করে দেয় (lazy evaluation).

def square(n):
    print(f"  হিসাব: {n}")
    return n * n

print("list comprehension:")
sq_list = [square(n) for n in range(3)]
print("generator expression:")
sq_gen = (square(n) for n in range(3))
print("এখনো কিছু হিসাব হয়নি")
print(next(sq_gen))
print(sum(sq_gen))      # বাকি দুটি
print(sum(sq_gen))      # খালি — একবারই চলে

evens = {n: n * n for n in range(6) if n % 2 == 0}
print(evens)

list comprehension সাথে সাথে তিনটি হিসাবই করে ফেলে। generator expression তৈরির সময় কিছুই হিসাব করে না — “এখনো কিছু হিসাব হয়নি” ছাপা হয় আগে। next চাইলে তবেই হিসাব: 0, তারপর sum বাকি দুটি নিয়ে 5 দেয়। দ্বিতীয় sum দেয় 0, কারণ generator শেষ (exhausted). শেষ লাইন: {0: 0, 2: 4, 4: 16}.

বিষয়List comprehensionGenerator expression
ফলপুরো listgenerator object
মেমরিn-এর সমানুপাতিকপ্রায় স্থির
কতবার পড়া যায়যতবার খুশি, index-ও চলেএকবার, index নেই
কখনফল বারবার লাগবে, len লাগবেএকবার পার হব — sum, any, বড় ফাইল

উপসংহার: comprehension লুপের চেয়ে সংক্ষিপ্ত আর সাধারণত একটু দ্রুত, কিন্তু তিন স্তরের nested comprehension পড়া কঠিন — তখন সাধারণ লুপ ভালো। side effect (print, ফাইল লেখা)-এর জন্য comprehension নয়। ফাংশনের একমাত্র argument হলে বাড়তি বন্ধনী লাগে না: sum(x * x for x in nums).

ঝালিয়ে নিন: পর্ব ১৮ · comprehension, পর্ব ৩৪ · generator

৫ · অংশ খ — বড় প্রোগ্রাম গোছানো (মডিউল ৪)

রিভিশনের জন্য: পর্ব ২২, পর্ব ২৩, পর্ব ২৪, পর্ব ২৫, পর্ব ২৬, পর্ব ২৭, পর্ব ২৮.

খ-০১Module আর package-এর পার্থক্য কী? if __name__ == "__main__": কেন লেখা হয়? (সহজ)

সংজ্ঞা: Module হলো একটি .py ফাইল — তার ভেতরের ফাংশন, class, ভ্যারিয়েবল import দিয়ে অন্য ফাইলে ব্যবহার করা যায়। Package হলো module-এর একটি ফোল্ডার (সাধারণত __init__.py-সহ), যা ভেতরে উপ-package-ও রাখতে পারে — যেমন shop/billing/invoice.py → import shop.billing.invoice.

import কীভাবে কাজ করে: প্রথমবার import করলে Python sys.path-এর ফোল্ডারগুলোতে ফাইল খোঁজে, পুরো module একবার চালায়, আর ফলটি sys.modules-এ রেখে দেয়। দ্বিতীয়বার import করলে আর চালায় না — জমা থাকা module ফেরত দেয়।

__main__ guard: প্রতিটি module-এর একটি __name__ থাকে। ফাইলটি সরাসরি চালালে (python report.py) এর মান "__main__"; অন্য ফাইল import করলে মান module-এর নাম ("report").

# report.py
def total(sales):
    return sum(sales)

if __name__ == "__main__":       # শুধু সরাসরি চালালে
    print(total([120, 80]))      # import করলে এটি চলবে না

উপসংহার: guard না দিলে কেউ from report import total লিখলেই পরীক্ষার কোড বা পুরো প্রোগ্রাম চলে যাবে। multiprocessing-এ (Windows-এ spawn পদ্ধতিতে) guard না থাকলে নতুন process আবার পুরো ফাইল চালিয়ে বিপদ ঘটায়। সতর্কতা: from x import * নাম গুলিয়ে দেয়; আর নিজের ফাইলের নাম random.py বা json.py রাখলে standard library-র module ঢেকে যায়।

ঝালিয়ে নিন: পর্ব ২২ · module, package আর import

খ-০২Virtual environment কী? pip-এর সাথে এটি কেন দরকার? (সহজ)

সংজ্ঞা: Virtual environment হলো একটি প্রজেক্টের জন্য আলাদা Python পরিবেশ — নিজস্ব site-packages ফোল্ডারসহ — যাতে এক প্রজেক্টের package অন্য প্রজেক্ট বা সিস্টেমের Python-এর সাথে মিশে না যায়। pip হলো PyPI থেকে package নামানোর টুল।

সমস্যাটা কী: প্রজেক্ট A-র দরকার কোনো লাইব্রেরির পুরোনো সংস্করণ, প্রজেক্ট B-র নতুন। একই জায়গায় ইনস্টল করলে একটি ভাঙবে (dependency hell). প্রতিটির নিজের venv থাকলে দুটোই ঠিক থাকে।

python -m venv .venv                  # তৈরি (standard library-র venv module)
source .venv/bin/activate             # Linux/macOS; Windows: .venv\Scripts\activate
python -m pip install requests        # এই venv-এ ইনস্টল
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt   # অন্য কম্পিউটারে হুবহু পরিবেশ

উপসংহার: .venv ফোল্ডার git-এ দেবেন না — দিন requirements.txt বা pyproject.toml. pip-এর বদলে python -m pip লিখলে নিশ্চিত হন ঠিক কোন Python-এ ইনস্টল হচ্ছে। আধুনিক টুল (uv, poetry) একই ধারণা আরও দ্রুত আর lock file-সহ দেয়। নিরাপত্তা: package-এর নামের বানান মিলিয়ে নিন — নকল নামের ক্ষতিকর package (typosquatting) আসলেই ঘটে।

ঝালিয়ে নিন: পর্ব ২৩ · pip, virtual environment আর pyproject

খ-০৩Python-এ exception handling ব্যাখ্যা করুন — try/except/else/finally, custom exception আর EAFP বনাম LBYL. (মাঝারি)

সংজ্ঞা: Exception হলো চলার সময়ের একটি ভুলের সংকেত, যা raise দিয়ে ছোড়া হয় আর call stack বেয়ে ওপরে উঠতে থাকে, যতক্ষণ না কোনো মিলে যাওয়া except তাকে ধরে। কেউ না ধরলে প্রোগ্রাম traceback দেখিয়ে থামে।

  • try — যে কোডে ভুল হতে পারে।
  • except X as e — X বা তার subclass ধরলে চলে। নির্দিষ্টটি আগে, সাধারণটি পরে।
  • else — try-তে কোনো exception না হলে চলে; সফল-পথের কোড এখানে রাখলে ভুল করে অন্য ভুল ধরা হয় না।
  • finally — সবসময় চলে; পরিষ্কারের কাজ (ফাইল বন্ধ, lock ছাড়া)।
class InsufficientBalance(Exception):
    """ব্যালেন্সের চেয়ে বেশি তুলতে চাইলে।"""
    def __init__(self, need, have):
        super().__init__(f"দরকার {need}, আছে {have}")
        self.need, self.have = need, have

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientBalance(amount, balance)
    return balance - amount

for amount in (300, 900, "abc"):
    try:
        left = withdraw(500, amount)
    except InsufficientBalance as e:
        print("ব্যর্থ:", e)
    except TypeError:
        print("ভুল টাইপ:", repr(amount))
    else:
        print("সফল, বাকি", left)
    finally:
        print("-- লেনদেন শেষ --")

৩০০ তোলায় else চলে — “সফল, বাকি 200”. ৯০০-তে নিজের বানানো custom exception ধরা পড়ে — “ব্যর্থ: দরকার 900, আছে 500”. "abc" > 500 তুলনা নিজেই TypeError ছোড়ে, তাই দ্বিতীয় except চলে। তিনবারই finally ছাপে “-- লেনদেন শেষ --”.

EAFP বনাম LBYL: LBYL (“look before you leap”) আগে যাচাই করে — if key in d: x = d[key]. EAFP (“easier to ask forgiveness than permission”) সরাসরি চেষ্টা করে, ভুল হলে ধরে — try: x = d[key] except KeyError: .... Python-এ EAFP প্রচলিত, কারণ যাচাই আর ব্যবহারের মাঝে অবস্থা বদলে যেতে পারে (ফাইল মুছে যাওয়া — race condition), আর সফল পথ পরিষ্কার থাকে।

উপসংহার: custom exception Exception থেকে বানান, BaseException থেকে নয়। খালি except: বা except Exception: pass ভুল গিলে ফেলে — এমনকি খালি except: Ctrl+C (KeyboardInterrupt)-ও ধরে। মূল কারণ রাখতে raise NewError(...) from err. Python 3.11 থেকে একসাথে কয়েকটি exception-এর জন্য ExceptionGroup আর except* আছে।

ঝালিয়ে নিন: পর্ব ২৪ · exception — ভুল সামলানো

খ-০৪Python-এ ফাইল পড়া-লেখার সঠিক পদ্ধতি লিখুন। with আর encoding কেন জরুরি? (সহজ)

সংজ্ঞা: open(path, mode, encoding=...) একটি file object দেয়। প্রধান mode: "r" পড়া (ডিফল্ট), "w" লেখা (আগের লেখা মুছে), "a" শেষে যোগ, "x" শুধু নতুন ফাইল; সাথে "b" দিলে binary (bytes), না দিলে text (str).

from pathlib import Path

path = Path("data") / "students.txt"
path.parent.mkdir(exist_ok=True)

with open(path, "w", encoding="utf-8") as f:
    f.write("রহিম,85\nকরিম,92\n")

with open(path, encoding="utf-8") as f:
    for line in f:                       # লাইন-বাই-লাইন, মেমরি বাঁচে
        name, mark = line.strip().split(",")
        print(name, int(mark))

with কেন: এটি একটি context manager — block থেকে বের হলেই ফাইল বন্ধ হয়, মাঝখানে exception হলেও। হাতে f.close() লিখলে exception-এর পথে সেটি বাদ পড়ে যেতে পারে, আর লেখা ডেটা buffer-এ থেকে হারাতে পারে।

encoding কেন: text mode-এ encoding না দিলে Python প্ল্যাটফর্মের ডিফল্ট (locale) encoding নেয় — Windows-এ অনেক সময় UTF-8 নয়। তখন বাংলা লেখা UnicodeDecodeError দেয় বা ভাঙা অক্ষর (mojibake) হয়ে যায়। তাই সবসময় encoding="utf-8" লিখুন।

উপসংহার: পথ জোড়া লাগাতে string নয়, pathlib; ছোট ফাইলে Path.read_text(encoding="utf-8"). বড় ফাইল একবারে read() নয়, লাইন-বাই-লাইন। CSV/JSON-এর জন্য csv আর json module ব্যবহার করুন, নিজে split(",") নয় — কমা-ভরা ঘর তখন ভাঙে। ব্যবহারকারীর দেওয়া পথ সরাসরি খুললে path traversal-এর ঝুঁকি।

ঝালিয়ে নিন: পর্ব ২৫ · ফাইল I/O, with আর pathlib, পর্ব ২৬ · CSV আর JSON

খ-০৫Regular expression কী? Python-এ re module-এর প্রধান ফাংশনগুলো উদাহরণসহ লিখুন। (মাঝারি)

সংজ্ঞা: Regular expression (regex) হলো লেখার প্যাটার্ন বর্ণনার একটি ছোট ভাষা — “০১ দিয়ে শুরু, তারপর ৯টি অঙ্ক”. প্রধান ফাংশন: re.search (যেকোনো জায়গায় প্রথম মিল), re.match (শুধু শুরুতে), re.fullmatch (পুরোটা মিলতে হবে), re.findall/re.finditer (সব মিল), re.sub (প্রতিস্থাপন), re.compile (বারবার ব্যবহারের জন্য)।

import re

text = "রহিম: 01712-345678, করিম: 01811-223344, ভুল: 0123"
phone = re.compile(r"\b(01[3-9])(\d{2})-(\d{6})\b")

for m in phone.finditer(text):
    print(m.group(0), "→ অপারেটর কোড", m.group(1))

print(re.findall(r"\d+", "দাম 120 টাকা, ছাড় 15%"))
print(re.sub(r"\s+", " ", "অনেক   ফাঁকা    জায়গা"))
ident = r"[A-Za-z_]\w*"
print(bool(re.fullmatch(ident, "user_1")), bool(re.fullmatch(ident, "1user")))

দুটি ফোন নম্বর মেলে — 01712-345678 → অপারেটর কোড 017 আর 01811-223344 → অপারেটর কোড 018; 0123 মেলে না। বন্ধনী capturing group বানায়, group(1) প্রথমটি দেয়। findall দেয় ['120', '15'], sub অনেক ফাঁকা জায়গাকে একটিতে নামায়, আর শেষ লাইন True False.

মূল চিহ্ন: \d অঙ্ক, \w অক্ষর/অঙ্ক/_, \s ফাঁকা, . যেকোনো একটি অক্ষর, * + ? কতবার, {m,n} নির্দিষ্ট সংখ্যা, ^ $ শুরু-শেষ, \b শব্দের সীমা। প্যাটার্ন সবসময় raw string (r"...")-এ লিখুন, নইলে \b backspace অক্ষর হয়ে যায়।

উপসংহার: Python-এর str প্যাটার্নে \d Unicode অঙ্কও মেলায় — বাংলা “১২০”-ও। শুধু ASCII অঙ্ক চাইলে [0-9] বা re.ASCII. .* greedy — যতটা পারে নেয়; কম নিতে .*?. খারাপ প্যাটার্ন বড় ইনপুটে চরম ধীর হতে পারে (ReDoS). সহজ কাজে (startswith, in, split) regex লাগে না।

ঝালিয়ে নিন: পর্ব ২৮ · regular expression

৬ · অংশ গ — অবজেক্ট-ওরিয়েন্টেড Python (মডিউল ৫)

রিভিশনের জন্য: পর্ব ২৯, পর্ব ৩০, পর্ব ৩১, পর্ব ৩২, পর্ব ৩৩, পর্ব ৩৪, পর্ব ৩৫.

গ-০১OOP-এর চারটি স্তম্ভ কী? Python-এ প্রতিটি কীভাবে দেখা যায়? (সহজ)

সংজ্ঞা: Object-oriented programming-এ ডেটা আর সেই ডেটার ওপর কাজ একসাথে একটি object-এ থাকে। চারটি স্তম্ভ:

  • Encapsulation: ডেটা ও method একসাথে, আর ভেতরের খুঁটিনাটি লুকানো। Python-এ কড়া private নেই — _balance মানে “ভেতরের, ছোঁবেন না” (প্রথা), __balance name mangling করে _Account__balance বানায়। নিয়ন্ত্রিত প্রবেশের জন্য @property.
  • Abstraction: ব্যবহারকারী কী করা যায় জানেন, কীভাবে হয় জানতে হয় না — account.withdraw(500). abc.ABC দিয়ে abstract class বানানো যায়।
  • Inheritance: class SavingsAccount(Account): — base-এর আচরণ নিয়ে বাড়ানো বা বদলানো। Python multiple inheritance সমর্থন করে, ক্রম ঠিক করে MRO.
  • Polymorphism: একই ডাক, object অনুযায়ী আলাদা কাজ। Python-এ এর জন্য inheritance বাধ্যতামূলক নয় — duck typing: যার area() আছে, তাকেই shape হিসেবে চালানো যায়। len() list, str, dict — সবার জন্য কাজ করে।
class Circle:
    def __init__(self, r): self.r = r
    def area(self): return 3.14 * self.r ** 2

class Square:
    def __init__(self, s): self.s = s
    def area(self): return self.s ** 2

for shape in (Circle(1), Square(2)):   # কোনো common base নেই
    print(shape.area())                # 3.14, তারপর 4

উপসংহার: Python-এ সবকিছুই object — int, ফাংশন, class নিজেও। OOP বাধ্যতামূলক নয়; ছোট script-এ ফাংশনই যথেষ্ট। গভীর inheritance গাছের বদলে প্রায়ই composition (“has-a”) ভালো।

ঝালিয়ে নিন: পর্ব ২৯ · class ও object, পর্ব ৩১ · inheritance বনাম composition

গ-০২Class attribute আর instance attribute-এর পার্থক্য উদাহরণসহ লিখুন। self কী? (মাঝারি)

সংজ্ঞা: Class attribute class-এর গায়ে থাকে, সব instance ভাগ করে। Instance attribute প্রতিটি object-এর নিজস্ব, সাধারণত __init__-এ self.x = ... দিয়ে বানানো। self কোনো keyword নয় — method-এর প্রথম parameter, যেখানে Python ডাকার object-টি পাঠায়: a.study() আসলে Student.study(a).

class Student:
    school = "ABCL স্কুল"        # class attribute — সবার ভাগে
    subjects = []                # সাবধান: mutable class attribute

    def __init__(self, name):
        self.name = name         # instance attribute — নিজের

a = Student("রাফি")
b = Student("নীলা")
a.school = "নতুন স্কুল"          # a-তে নতুন instance attribute
print(a.school, "|", b.school, "|", Student.school)

a.subjects.append("গণিত")        # নতুন নাম নয় — ভাগের list বদলাল
print(b.subjects, Student.subjects)
print(vars(a))
print(vars(b))

প্রথম লাইন: নতুন স্কুল | ABCL স্কুল | ABCL স্কুল — a.school = ... class-এরটা বদলায়নি, a-র নিজের একটি attribute বানিয়ে class-এরটা ঢেকে দিয়েছে। কিন্তু a.subjects.append কোনো নাম বাঁধে না, ভাগের list-কেই বদলায় — তাই b-ও ['গণিত'] দেখে। vars(a) দেখায় {'name': 'রাফি', 'school': 'নতুন স্কুল'}, vars(b) শুধু {'name': 'নীলা'}.

খোঁজার নিয়ম: obj.attr পড়লে Python আগে object-এর নিজের __dict__-এ খোঁজে, না পেলে class-এ, তারপর base class-গুলোতে (MRO ক্রমে)। লেখার সময় (obj.attr = v) সবসময় object-এর নিজের dict-এ লেখে।

উপসংহার: class attribute ধ্রুবক বা সবার ভাগের তথ্যের জন্য (যেমন interest_rate = 0.07, বা মোট object গোনা — তখন Student.count += 1 লিখুন, self.count নয়)। mutable class attribute প্রায় সবসময় বাগ — প্রতিটি object-এর নিজের list চাইলে __init__-এ বানান।

ঝালিয়ে নিন: পর্ব ২৯ · class ও object

গ-০৩Dunder method কী? Python-এর data model উদাহরণসহ ব্যাখ্যা করুন। __str__ আর __repr__-এর পার্থক্য কী? (মাঝারি)

সংজ্ঞা: Dunder method (double underscore, যেমন __add__) হলো বিশেষ নামের method, যা Python নিজে ডাকে যখন আপনি কোনো সাধারণ syntax ব্যবহার করেন। a + b → a.__add__(b), len(x) → x.__len__(), x[i] → x.__getitem__(i). এই নিয়মগুলোর সমষ্টিই Python data model — এর জোরেই নিজের class built-in টাইপের মতো আচরণ করে।

class Taka:
    def __init__(self, amount):
        self.amount = amount
    def __repr__(self):
        return f"Taka({self.amount})"
    def __str__(self):
        return f"৳{self.amount}"
    def __add__(self, other):
        if not isinstance(other, Taka):
            return NotImplemented
        return Taka(self.amount + other.amount)
    def __eq__(self, other):
        return isinstance(other, Taka) and self.amount == other.amount
    def __lt__(self, other):
        return self.amount < other.amount
    def __bool__(self):
        return self.amount != 0

wallet = [Taka(500), Taka(120), Taka(0)]
print(wallet)
print(Taka(500) + Taka(120))
print(Taka(5) == Taka(5), sorted(wallet), bool(Taka(0)))
print(sum(wallet, Taka(0)))

list ছাপালে ভেতরের object-এর __repr__ দেখা যায়: [Taka(500), Taka(120), Taka(0)]. print একটি object-এর __str__ নেয়: ৳620. তৃতীয় লাইন True [Taka(0), Taka(120), Taka(500)] False — sorted কাজ করেছে __lt__ দিয়ে, bool __bool__ দিয়ে। শেষে sum শুরু করে Taka(0) থেকে, বারবার __add__ ডেকে ৳620.

__str__ বনাম __repr__: __str__ ব্যবহারকারীর জন্য সুন্দর লেখা (print, str(), f-string); __repr__ developer-এর জন্য স্পষ্ট, দ্ব্যর্থহীন রূপ — সম্ভব হলে এমন, যা কপি করে আবার object বানানো যায়। REPL, debugger আর container-এর ভেতরে repr দেখা যায়। শুধু __repr__ লিখলে str()-ও সেটিই ব্যবহার করে।

উপসংহার: অজানা টাইপ পেলে exception নয়, NotImplemented ফেরত দিন — তখন Python অন্য পক্ষের reflected method (__radd__) চেষ্টা করে। __eq__ লিখলে Python __hash__-কে None করে দেয় (object তখন unhashable); dict key বানাতে চাইলে __hash__-ও লিখুন। সব তুলনা অপারেটর চাইলে functools.total_ordering.

ঝালিয়ে নিন: পর্ব ৩০ · dunder method — Python-এর data model

গ-০৪Python-এ inheritance, MRO আর super() কীভাবে কাজ করে? diamond problem কীভাবে সমাধান হয়? (কঠিন)

সংজ্ঞা: Inheritance-এ derived class base-এর attribute ও method পায়, চাইলে override করে। MRO (method resolution order) হলো সেই ক্রম, যে ক্রমে Python একটি attribute খোঁজে — Class.__mro__ দেখায়। Python 2.3 থেকে এই ক্রম বানানো হয় C3 linearization নিয়মে: প্রতিটি class তার base-গুলোর আগে আসে, আর base-গুলো যে ক্রমে লেখা, সেই ক্রম রক্ষা হয়।

super() “parent” নয় — MRO-তে পরের class. এই সূক্ষ্ম পার্থক্যটাই diamond-কে ঠিক রাখে।

class Base:
    def greet(self):
        return ["Base"]

class Left(Base):
    def greet(self):
        return ["Left"] + super().greet()

class Right(Base):
    def greet(self):
        return ["Right"] + super().greet()

class Child(Left, Right):
    def greet(self):
        return ["Child"] + super().greet()

print([cls.__name__ for cls in Child.__mro__])
print(Child().greet())
print(isinstance(Child(), Right), issubclass(Left, Right))

MRO: ['Child', 'Left', 'Right', 'Base', 'object']. greet-এর ফল ['Child', 'Left', 'Right', 'Base'] — Left-এর ভেতরের super() Base-এ নয়, Right-এ গেছে, কারণ Child-এর MRO-তে Left-এর পরে Right. ফলে Base একবারই চলে। শেষ লাইন True False: Child একটি Right, কিন্তু Left আর Right-এর মধ্যে কোনো সম্পর্ক নেই।

        Base
       /    \
    Left    Right        MRO: Child → Left → Right → Base → object
       \    /
        Child

উপসংহার: সব class যদি super() ব্যবহার করে (cooperative multiple inheritance), diamond-এ প্রতিটি method ঠিক একবার চলে। কেউ Base.greet(self) সরাসরি ডাকলে শিকল ভাঙে। C3 নিয়মে মেলানো অসম্ভব এমন ক্রম লিখলে class তৈরির সময়ই TypeError. বাস্তবে multiple inheritance ছোট mixin-এর জন্য রাখুন; বাকিতে composition.

ঝালিয়ে নিন: পর্ব ৩১ · inheritance বনাম composition

গ-০৫@property কী, কেন? dataclass কী সুবিধা দেয়? (মাঝারি)

সংজ্ঞা: property একটি method-কে attribute-এর মতো দেখায় — acc.balance পড়লে আসলে একটি getter ফাংশন চলে, acc.balance = x লিখলে setter চলে, যেখানে যাচাই করা যায়। dataclass (Python 3.7 থেকে, @dataclasses.dataclass) টাইপ-চিহ্নিত field দেখে নিজে থেকে __init__, __repr__, __eq__ বানিয়ে দেয়।

from dataclasses import dataclass, field

class Account:
    def __init__(self, balance):
        self.balance = balance          # এখানেও setter চলে
    @property
    def balance(self):
        return self._balance
    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("ব্যালেন্স ঋণাত্মক হতে পারে না")
        self._balance = value

@dataclass(frozen=True)
class Product:
    name: str
    price: float
    tags: list[str] = field(default_factory=list)   # [] সরাসরি নয়

p = Product("চা", 15.0)
print(p)            # Product(name='চা', price=15.0, tags=[])

property কেন: শুরুতে সাধারণ attribute দিয়ে শুরু করুন; পরে যাচাই লাগলে property-তে বদলান — ব্যবহারকারীর কোড (acc.balance) একটুও বদলাতে হয় না। Java-র মতো সব জায়গায় get_x()/set_x() লেখা Python-এ অপ্রয়োজনীয়।

উপসংহার: dataclass মূলত ডেটা রাখার class-এর boilerplate দূর করে; frozen=True দিলে immutable (বদলাতে গেলে FrozenInstanceError) আর hashable হয়; order=True তুলনা দেয়; slots=True (3.10 থেকে) মেমরি বাঁচায়। mutable default-এর জন্য field(default_factory=list) বাধ্যতামূলক — tags: list = [] লিখলে dataclass নিজেই ValueError দেয়। dataclass চলার সময় টাইপ যাচাই করে না; তার জন্য pydantic-এর মতো লাইব্রেরি।

ঝালিয়ে নিন: পর্ব ৩২ · property, dataclass আর descriptor

গ-০৬Duck typing, abstract base class (ABC) আর Protocol-এর পার্থক্য লিখুন। কখন কোনটি? (কঠিন)

সংজ্ঞা: তিনটিই “এই object কী করতে পারে” প্রকাশের উপায়, তিন মাত্রার কড়াকড়িতে।

বিষয়Duck typingABCProtocol
মূল ধারণাmethod থাকলেই চলবেঘোষিত base থেকে inherit করতে হবে (nominal)method-এর আকার মিললেই চলবে (structural)
যাচাই কখনডাকার মুহূর্তে (না থাকলে AttributeError)object বানানোর সময় — abstract method বাকি থাকলে TypeErrortype checker (mypy), চলার আগে
inherit লাগে?নাহ্যাঁ (বা register)না
কোথা থেকেভাষার স্বভাবabc moduletyping.Protocol, Python 3.8 থেকে
from abc import ABC, abstractmethod
from typing import Protocol

class PaymentBase(ABC):
    @abstractmethod
    def pay(self, amount: int) -> str: ...

class Payer(Protocol):
    def pay(self, amount: int) -> str: ...

class Bkash:                         # কোনো base নেই
    def pay(self, amount: int) -> str:
        return f"bKash-এ {amount} টাকা"

def checkout(method: Payer) -> None:  # mypy: Bkash চলবে
    print(method.pay(500))

checkout(Bkash())
# PaymentBase()  →  TypeError: Can't instantiate abstract class ...

উপসংহার: নিজের কোডের ভেতরে ছোট কাজে duck typing-ই যথেষ্ট। একটি পরিবারের class-এর জন্য বাধ্যতামূলক method আর ভাগের কোড চাইলে ABC (collections.abc-এর Iterable, Mapping এর উদাহরণ)। অন্যের লেখা class-কেও (যাকে বদলাতে পারবেন না) type hint-এ গ্রহণ করতে চাইলে Protocol. Protocol ডিফল্টে চলার সময় কিছু যাচাই করে না; @runtime_checkable দিলে isinstance শুধু method-এর নাম আছে কি না দেখে, signature নয়।

ঝালিয়ে নিন: পর্ব ৩৩ · ABC, duck typing আর Protocol

গ-০৭Iterator আর generator-এর পার্থক্য কী? yield কীভাবে কাজ করে? (মাঝারি)

সংজ্ঞা: Iterable হলো যার ওপর লুপ চালানো যায় (__iter__ আছে) — list, str, dict, ফাইল। Iterator হলো সেই object, যা একটি একটি করে মান দেয়: __next__ পরের মান দেয়, শেষ হলে StopIteration ছোড়ে, আর __iter__ নিজেকেই ফেরত দেয় (iterator protocol). Generator হলো iterator বানানোর সহজতম উপায় — যে ফাংশনে yield আছে, তাকে ডাকলে শরীর চলে না, একটি generator object ফেরত আসে।

class Countdown:                  # হাতে লেখা iterator
    def __init__(self, start):
        self.n = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.n <= 0:
            raise StopIteration
        self.n -= 1
        return self.n + 1

def countdown(n):                 # একই কাজ, generator দিয়ে
    while n > 0:
        yield n
        n -= 1

print(list(Countdown(3)), list(countdown(3)))
g = countdown(2)
print(next(g), next(g))
print(next(g, "শেষ"))
it = iter([10, 20])
print(next(it), iter(it) is it)

দুটোই [3, 2, 1] দেয় — কিন্তু generator-এ __iter__, __next__, StopIteration কিছুই হাতে লিখতে হয়নি। next(g) দুবার দেয় 2 1; তৃতীয়বার generator শেষ, তাই default মান শেষ. শেষ লাইন 10 True: list-এর iterator-এর iter() সে নিজেই।

yield কীভাবে: প্রতিটি next()-এ ফাংশন চলে পরের yield পর্যন্ত, মান দেয়, আর সেখানেই থেমে থাকে — local ভ্যারিয়েবল আর কোন লাইনে ছিল, সব মনে রেখে। পরের next() ঠিক সেখান থেকে চালায়। ফাংশন শেষ হলে আপনাআপনি StopIteration.

উপসংহার: generator মেমরিতে পুরো ডেটা রাখে না, তাই কোটি লাইনের লগ ফাইল বা অসীম ধারা সামলাতে পারে, আর কয়েকটি generator জুড়ে pipeline বানানো যায়। সীমা: একবারই পার হওয়া যায়, len বা index নেই। yield from sub() আরেকটি generator-এর সব মান পাঠায়।

ঝালিয়ে নিন: পর্ব ৩৪ · iterator, generator আর yield

গ-০৮Decorator কী? functools.wraps কেন দরকার? argument-সহ decorator কীভাবে লেখেন? (কঠিন)

সংজ্ঞা ও ব্যাখ্যা: অংশ ২-এর ডায়াগ্রামে পুরো মডেল উত্তর আছে। সারকথা: decorator একটি higher-order function — ফাংশন নেয়, নতুন ফাংশন ফেরত দেয়; @d মানে f = d(f), যা ঘটে def চলার সময়।

import functools

def shout(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{func.__name__}] শুরু")
        result = func(*args, **kwargs)
        print(f"[{func.__name__}] শেষ")
        return result
    return wrapper

@shout
def add(a, b):
    """দুটি সংখ্যা যোগ করে"""
    return a + b

print(add(2, 3))
print(add.__name__, "|", add.__doc__)

আউটপুট: [add] শুরু, [add] শেষ, 5, তারপর add | দুটি সংখ্যা যোগ করে. @functools.wraps(func) লাইনটি মুছে চালালে শেষ লাইন হবে wrapper | None — নাম আর docstring হারিয়ে যায়, ফলে লগ, debugger, documentation টুল সব ভুল নাম দেখায়।

Argument-সহ decorator: @retry(times=3)-এ আগে retry(3) চলে, সে একটি decorator ফেরত দেয়; সেই decorator ফাংশন নিয়ে wrapper ফেরত দেয় — তিন স্তরের ফাংশন (retry → decorator → wrapper).

উপসংহার: একাধিক decorator stack করলে নিচেরটি আগে লাগে (@a @b def f = a(b(f))), কিন্তু ডাকার সময় ওপরেরটির কোড আগে চলে। standard library-র তৈরি decorator: @property, @classmethod, @staticmethod, @functools.lru_cache, @dataclass. wrapper-এ ফলটি return করতে ভুলবেন না — নইলে সব ডাক None দেবে।

ঝালিয়ে নিন: পর্ব ৩৫ · decorator

৭ · অংশ ঘ — আধুনিক Python ও বাস্তব প্রয়োগ (মডিউল ৬–৭)

রিভিশনের জন্য: পর্ব ৩৬, পর্ব ৩৭, পর্ব ৩৯, পর্ব ৪১, পর্ব ৪২, পর্ব ৪৩, পর্ব ৪৪, পর্ব ৫০.

ঘ-০১Context manager কী? with statement ভেতরে কীভাবে কাজ করে? নিজে একটি লিখে দেখান। (মাঝারি)

সংজ্ঞা: Context manager হলো এমন object, যা একটি কাজের আগে প্রস্তুতি আর পরে পরিষ্কারের দায়িত্ব নেয় — পরিষ্কার হবেই, block স্বাভাবিকভাবে শেষ হোক বা exception হোক। এটি দুটি method দিয়ে চলে: __enter__ আর __exit__.

with যা করে: (১) __enter__() ডাকে, ফল as-এর নামে বাঁধে; (২) block চালায়; (৩) যেভাবেই বের হোক, __exit__(exc_type, exc, tb) ডাকে — exception না হলে তিনটিই None. __exit__ True ফেরত দিলে exception চাপা পড়ে, নইলে ওপরে যায়।

from contextlib import contextmanager

class Timer:
    def __enter__(self):
        print("শুরু")
        return self
    def __exit__(self, exc_type, exc, tb):
        print("শেষ, ভুল:", exc_type)
        return False                    # exception চাপা দেব না

@contextmanager
def tag(name):
    print(f"<{name}>")
    try:
        yield                           # এখানে with-এর block চলে
    finally:
        print(f"</{name}>")

with Timer():
    print("কাজ চলছে")                  # শুরু, কাজ চলছে, শেষ, ভুল: None
with tag("b"):
    print("মোটা লেখা")                 # <b>, মোটা লেখা, </b>

উপসংহার: বাস্তব উদাহরণ: open(), threading.Lock, ডেটাবেস transaction (সফল হলে commit, ভুলে rollback), decimal.localcontext(), contextlib.suppress. এটি C++-এর RAII-এর Python রূপ, তবে নির্ভর করে block-এর ওপর, object-এর জীবনের ওপর নয়। @contextmanager-এ yield-কে try/finally-তে না মুড়লে exception-এর সময় পরিষ্কারের অংশ চলবে না। async কোডে async with (__aenter__/__aexit__)।

ঝালিয়ে নিন: পর্ব ৩৬ · context manager

ঘ-০২Type hint কী? এগুলো কি চলার সময় টাইপ যাচাই করে? mypy কী কাজে লাগে? (মাঝারি)

সংজ্ঞা: Type hint হলো ভ্যারিয়েবল, parameter আর return-এর প্রত্যাশিত টাইপের ঘোষণা — def total(prices: list[float]) -> float:. Python 3.5-এ typing module আসে (PEP 484). গুরুত্বপূর্ণ কথা: Python interpreter চলার সময় এগুলো যাচাই করে না — ভুল টাইপ দিলেও কোড চলে।

def greet(name: str, times: int = 1) -> str:
    return ("হ্যালো " + name + "! ") * times

print(greet("রিনা", 2))     # ঠিক
print(greet("রিনা", "2"))   # চলার সময় TypeError, কিন্তু hint-এর কারণে নয় —
                             # str * str অবৈধ বলে। mypy চালানোর আগেই ধরত।

mypy (বা pyright) হলো static type checker — কোড না চালিয়েই hint মিলিয়ে ভুল খোঁজে: mypy app.py. আধুনিক রূপ: list[int] (3.9 থেকে), int | None (3.10 থেকে), type alias বিবৃতি ও def first[T](xs: list[T]) -> T (3.12 থেকে)। Python 3.14-এ annotation আর সাথে সাথে হিসাব হয় না, দরকারের সময় হিসাব হয় (PEP 649) — ফলে আগে-সংজ্ঞায়িত-নয় এমন class-এর নাম লিখতে উদ্ধৃতিচিহ্ন প্রায়ই লাগে না।

উপসংহার: সুবিধা — editor-এর autocomplete, চলার আগেই বাগ ধরা, কোড নিজেই documentation, বড় দলে নিরাপদ refactoring. Hint ঐচ্ছিক (gradual typing) — ধীরে ধীরে যোগ করা যায়। চলার সময় সত্যিকারের যাচাই লাগলে (API-র ইনপুট) pydantic-এর মতো লাইব্রেরি। Any সবকিছু মেনে নেয়, তাই বেশি ব্যবহার করলে hint-এর লাভ হারায়।

ঝালিয়ে নিন: পর্ব ৩৭ · type hint আর mypy

ঘ-০৩GIL কী? threading, multiprocessing আর asyncio-র পার্থক্য লিখুন — কোন কাজে কোনটি? (কঠিন)

সংজ্ঞা: GIL (Global Interpreter Lock) হলো CPython-এর একটি lock, যার কারণে একটি process-এ একসাথে একটিই thread Python bytecode চালাতে পারে। এটি CPython-এর reference counting-কে সহজ ও নিরাপদ রাখে। ফল: CPU-ভারী খাঁটি Python কাজ বেশি thread দিয়ে দ্রুত হয় না। কিন্তু I/O-র অপেক্ষায় (নেটওয়ার্ক, ফাইল, time.sleep) thread GIL ছেড়ে দেয়, তাই I/O-ভারী কাজে thread কাজে লাগে।

বিষয়threadingmultiprocessingasyncio
কী চালায়এক process-এ কয়েকটি OS threadকয়েকটি আলাদা process, প্রতিটির নিজের interpreter ও GILএক thread-এ একটি event loop, অনেক coroutine
পালা বদলOS ঠিক করে (preemptive)OS, সত্যিকারের সমান্তরালকোড নিজে await-এ ছাড়ে (cooperative)
সেরা কাজI/O-bound, অল্প সংখ্যক কাজCPU-bound হিসাবহাজারো নেটওয়ার্ক সংযোগ
মেমরি ভাগসহজ, কিন্তু race condition-এর ঝুঁকি — lock লাগেভাগ হয় না; ডেটা pickle করে পাঠাতে হয়এক thread, তাই race কম; কিন্তু blocking ডাক পুরো loop আটকায়
খরচমাঝারিবেশি (process চালু করা)খুব কম প্রতি কাজে

উদাহরণ: ১০০টি ওয়েবপেজ নামানো — ThreadPoolExecutor বা asyncio; ১০ লাখ ছবির আকার বদলানো — ProcessPoolExecutor; চ্যাট সার্ভার — asyncio.

উপসংহার: GIL ভাষার নিয়ম নয়, CPython-এর বাস্তবায়ন। Python 3.13-এ পরীক্ষামূলক free-threaded build আসে (PEP 703), আর 3.14-এ এটি আনুষ্ঠানিকভাবে সমর্থিত — তবে এখনো আলাদা build, ডিফল্ট নয়। GIL থাকলেও count += 1-এর মতো কাজ thread-safe নয়, কারণ এটি কয়েকটি bytecode-এ ভাঙে — lock লাগবে। NumPy-র মতো C লাইব্রেরি ভারী হিসাবের সময় GIL ছেড়ে দিতে পারে।

ঝালিয়ে নিন: পর্ব ৪১ · thread, process আর GIL, পর্ব ৪২ · asyncio

ঘ-০৪Unit testing কী? pytest দিয়ে একটি ফাংশনের test লিখে দেখান — fixture আর parametrize-সহ। (মাঝারি)

সংজ্ঞা: Unit test হলো কোডের সবচেয়ে ছোট অংশ (একটি ফাংশন বা class) আলাদা করে স্বয়ংক্রিয়ভাবে যাচাইয়ের কোড। pytest হলো Python-এর সবচেয়ে জনপ্রিয় test framework (third-party; standard library-তে আছে unittest). pytest-এ সাধারণ assert লিখলেই চলে — ব্যর্থ হলে সে দুই পাশের মান দেখায়।

# test_discount.py   —   চালাতে: pytest -q
import pytest

def discount(price, percent):
    if not 0 <= percent <= 100:
        raise ValueError("percent 0–100 হতে হবে")
    return round(price * (100 - percent) / 100, 2)

@pytest.fixture
def cart():                       # প্রতিটি test-এর জন্য নতুন ডেটা
    return [100, 250, 50]

@pytest.mark.parametrize("price, percent, expected", [
    (100, 10, 90.0),
    (100, 0, 100.0),
    (99.99, 100, 0.0),            # সীমার মান
])
def test_discount(price, percent, expected):
    assert discount(price, percent) == expected

def test_cart_total(cart):
    assert sum(discount(p, 50) for p in cart) == 200.0

def test_bad_percent():
    with pytest.raises(ValueError):
        discount(100, 120)

ব্যাখ্যা: pytest test_ দিয়ে শুরু ফাইল আর ফাংশন নিজে খুঁজে নেয়। fixture প্রস্তুতির কোড — parameter-এর নাম মিলিয়ে pytest নিজেই দেয়। parametrize একই test অনেক ইনপুটে চালায় — এখানে তিনটি আলাদা test হিসেবে গোনা হয়। pytest.raises যাচাই করে ঠিক exception এসেছে কি না।

উপসংহার: ভালো test দ্রুত, স্বাধীন (একটি আরেকটির ওপর নির্ভর করে না) আর নির্ধারিত (সময় বা random-এ বদলায় না)। happy path ছাড়াও সীমার মান আর ভুল ইনপুট test করুন। float তুলনায় pytest.approx. নেটওয়ার্ক বা ডেটাবেস বাদ দিতে monkeypatch/mock. CI-তে প্রতিটি push-এ test চালান।

ঝালিয়ে নিন: পর্ব ৩৯ · টেস্টিং — pytest

ঘ-০৫“Pythonic কোড” বলতে কী বোঝায়? PEP 8-এর প্রধান নিয়ম আর কয়েকটি idiom উদাহরণসহ লিখুন। (সহজ)

সংজ্ঞা: Pythonic কোড মানে এমন কোড, যা Python-এর নিজস্ব সুবিধা আর প্রথা কাজে লাগায় — অন্য ভাষার ধাঁচ হুবহু অনুবাদ নয়। মূল দর্শন Zen of Python-এ (import this): “Readability counts”, “Explicit is better than implicit”. PEP 8 হলো Python-এর আনুষ্ঠানিক style guide.

PEP 8-এর প্রধান নিয়ম: ৪ space indentation (tab নয়); ফাংশন ও ভ্যারিয়েবল snake_case, class PascalCase, ধ্রুবক UPPER_SNAKE_CASE; লাইন ৭৯ অক্ষরের মধ্যে (অনেক দল ৮৮–১০০ মানে); import ফাইলের ওপরে, standard → third-party → নিজের — এই ক্রমে; None-এর সাথে তুলনা is None.

অন্য ভাষার ধাঁচPythonic
for i in range(len(xs)): print(i, xs[i])for i, x in enumerate(xs): print(i, x)
t = a; a = b; b = ta, b = b, a
if len(xs) == 0:if not xs:
লুপে s += part"".join(parts)
if k in d: v = d[k] else: v = 0v = d.get(k, 0)
f = open(p); ...; f.close()with open(p, encoding="utf-8") as f:

উপসংহার: হাতে নিয়ম মনে রাখার বদলে টুল ব্যবহার করুন — ruff (linter ও formatter) বা black. তবে Pythonic মানে “সবচেয়ে চালাক এক লাইন” নয়; পাঁচ স্তরের nested comprehension Pythonic নয়। লক্ষ্য: যিনি পড়বেন, তিনি যেন দ্রুত বোঝেন।

ঝালিয়ে নিন: পর্ব ৪৪ · Pythonic কোড — PEP 8, idiom আর ruff

ঘ-০৬Python অ্যাপে SQL injection কীভাবে ঘটে, কীভাবে ঠেকাবেন? password আর API key কীভাবে সামলাবেন? (মাঝারি)

সংজ্ঞা: SQL injection ঘটে যখন ব্যবহারকারীর ইনপুট সরাসরি SQL string-এ জোড়া লাগানো হয় — তখন ইনপুট ডেটা না থেকে কমান্ডের অংশ হয়ে যায়।

import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT, phone TEXT)")
conn.execute("INSERT INTO users VALUES ('rahim', '017'), ('karim', '018')")

name = "x' OR '1'='1"                                  # আক্রমণকারীর ইনপুট
bad = f"SELECT * FROM users WHERE name = '{name}'"     # বিপজ্জনক
print(len(conn.execute(bad).fetchall()))               # 2 — সব ডেটা ফাঁস!

good = "SELECT * FROM users WHERE name = ?"            # parameterized
print(len(conn.execute(good, (name,)).fetchall()))     # 0 — ইনপুট শুধু ডেটা

সমাধান: সবসময় parameterized query (? বা %s placeholder, driver অনুযায়ী) — f-string, + বা % দিয়ে SQL নয়। ORM (SQLAlchemy, Django ORM) ভেতরে এটিই করে। একই নীতি shell-এ: subprocess.run([...]) list দিয়ে, shell=True-তে ব্যবহারকারীর ইনপুট নয়; আর অবিশ্বস্ত ডেটায় eval বা pickle.load কখনো নয়।

Secret সামলানো: API key, ডেটাবেস password কোডে বা git-এ নয় — environment variable বা .env ফাইলে (.gitignore-এ রেখে), পড়ুন os.environ["API_KEY"] দিয়ে। ব্যবহারকারীর password কখনো সরাসরি বা সাধারণ hash (MD5, SHA-256) দিয়ে নয় — salt-সহ ধীর hash: hashlib.scrypt, pbkdf2_hmac, বা bcrypt/argon2 লাইব্রেরি। OTP বা token বানাতে secrets module, random নয় — random নিরাপত্তার জন্য বানানো নয়, তার পরের মান অনুমান করা যায়।

উপসংহার: নিয়ম: ইনপুটকে কখনো বিশ্বাস করবেন না, কোড আর ডেটা আলাদা রাখুন, আর ন্যূনতম অনুমতি দিন। dependency-র পরিচিত দুর্বলতা খুঁজতে pip-audit.

ঝালিয়ে নিন: পর্ব ৫০ · নিরাপদ Python, পর্ব ২৭ · secrets ও sqlite3

ঘ-০৭একটি Python প্রোগ্রাম ধীর চলছে। কীভাবে খুঁজবেন আর দ্রুত করবেন? (মাঝারি)

মূল নীতি: অনুমান নয়, মাপুন। Donald Knuth-এর বিখ্যাত কথা — “premature optimization is the root of all evil” — মানে না মেপে দ্রুত করার চেষ্টা প্রায়ই ভুল জায়গায় সময় নষ্ট করে। প্রায়ই অল্প কয়েকটি লাইনেই সময়ের বেশিরভাগ যায় (hotspot).

  1. মাপুন: পুরো প্রোগ্রামে profiler — python -m cProfile -s cumtime app.py; ছোট টুকরোর তুলনায় timeit; মেমরিতে tracemalloc.
  2. অ্যালগরিদম আর ডেটা স্ট্রাকচার আগে: list-এ বারবার in → set; nested লুপে O(n²) → dict দিয়ে O(n); বারবার একই হিসাব → functools.lru_cache. এখানেই সবচেয়ে বড় লাভ।
  3. built-in আর লাইব্রেরি: sum, sorted, "".join, collections.Counter C-তে লেখা; সংখ্যার বড় হিসাবে NumPy-র vectorization.
  4. কাজের ধরন বুঝে সমান্তরাল: I/O-তে asyncio বা thread, CPU-তে process (ঘ-০৩)।
  5. শেষ উপায়: PyPy, Cython, বা Rust/C-তে একটি অংশ লেখা।

উপসংহার: প্রতিটি পরিবর্তনের আগে-পরে মাপুন, আর test চালিয়ে নিশ্চিত হোন যে দ্রুত করতে গিয়ে ফল বদলায়নি। পড়ার সুবিধা নষ্ট করে ছোটখাটো micro-optimization সাধারণত লাভের চেয়ে ক্ষতি বেশি করে।

ঝালিয়ে নিন: পর্ব ৪৩ · পারফরম্যান্স — মেপে দ্রুত করা, পর্ব ৪৫ · অ্যালগরিদম

৮ · অংশ ঙ — ভাইভা কুইকফায়ার (৭৪টি প্রশ্ন)

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

ভিত্তি, মূল ভাষা আর ডেটা স্ট্রাকচার (পর্ব ০১–২১)

রিভিশনের জন্য: পর্ব ০১, পর্ব ০৫, পর্ব ০৬, পর্ব ০৭, পর্ব ০৮, পর্ব ০৯, পর্ব ১১, পর্ব ১২, পর্ব ১৩, পর্ব ১৪, পর্ব ১৬, পর্ব ১৯, পর্ব ২০.

ভ-০১Python কে বানিয়েছেন? প্রথম প্রকাশ কবে? (সহজ)

Guido van Rossum; ১৯৮৯ সালের শেষে কাজ শুরু, প্রথম প্রকাশ ১৯৯১ সালে। নামটি এসেছে ব্রিটিশ কমেডি “Monty Python's Flying Circus” থেকে, সাপ থেকে নয়। পর্ব ০১

ভ-০২CPython কী? Python আর CPython কি একই? (সহজ)

Python একটি ভাষা; CPython হলো C-তে লেখা তার মূল ও সবচেয়ে প্রচলিত বাস্তবায়ন (python.org থেকে যা নামান)। অন্য বাস্তবায়ন: PyPy, MicroPython. পর্ব ০৫

ভ-০৩.pyc ফাইল কী? (সহজ)

import করা module-এর compile করা bytecode, __pycache__ ফোল্ডারে রাখা — পরের বার source না বদলালে আবার compile করতে হয় না। এটি machine code নয়, আর এতে কোড গোপন হয় না। পর্ব ০৫

ভ-০৪Python কি strongly typed নাকি weakly typed? (মাঝারি)

Dynamically কিন্তু strongly typed — টাইপ চলার সময় ঠিক হয়, কিন্তু নিজে থেকে অদ্ভুত রূপান্তর করে না: "5" + 3 দেয় TypeError, JavaScript-এর মতো "53" নয়। পর্ব ০৬

ভ-০৫is আর ==-এর পার্থক্য? (সহজ)

== মান সমান কি না দেখে (__eq__); is দেখে দুটি নাম একই object-কে দেখায় কি না। [] == [] True, কিন্তু [] is [] False. is শুধু None-এর মতো singleton-এর জন্য। পর্ব ০৮

ভ-০৬ফলো-আপ: a = 256; b = 256; a is b True হয় কেন? (কঠিন)

CPython −৫ থেকে ২৫৬ পর্যন্ত int আগে থেকে বানিয়ে রাখে আর পুনর্ব্যবহার করে (small int cache). এটি বাস্তবায়নের খুঁটিনাটি, ভাষার নিশ্চয়তা নয় — তাই সংখ্যার তুলনায় কখনো is নয়; literal-এর সাথে is লিখলে Python SyntaxWarning-ও দেয়। পর্ব ০৮

ভ-০৭0.1 + 0.2 == 0.3 কেন False? (সহজ)

float IEEE 754 binary-তে থাকে, আর 0.1 binary-তে ঠিকভাবে লেখা যায় না; যোগফল 0.30000000000000004. তুলনায় math.isclose, টাকার হিসাবে decimal.Decimal বা পয়সায় int. পর্ব ০৭

ভ-০৮/ আর //-এর পার্থক্য? 7 // -2 কত? (মাঝারি)

/ সবসময় float দেয় (10 / 2 → 5.0); // floor division — নিচের দিকে (−∞-এর দিকে) গোল করে। তাই 7 // -2 → -4, আর -7 % 3 → 2 (ভাগশেষ ভাজকের চিহ্ন নেয়)। C-তে 7 / -2 হয় −৩, কারণ সেখানে ভাগ শূন্যের দিকে ছাঁটা হয়। পর্ব ০৭

ভ-০৯round(2.5) কত? (মাঝারি)

2. Python “banker's rounding” মানে — ঠিক মাঝখানে থাকলে জোড় সংখ্যার দিকে যায়; round(3.5) → 4. এতে অনেক হিসাবের গড়ে একদিকে ঝোঁক কমে। পর্ব ০৭

ভ-১০Python-এ int কত বড় হতে পারে? (সহজ)

যত বড় মেমরি দেয় (arbitrary precision) — 2 ** 1000 সরাসরি হিসাব হয়, C-এর মতো overflow নেই। দাম: বড় সংখ্যায় হিসাব ধীর। পর্ব ০৭

ভ-১১কোন কোন মান falsy? (সহজ)

False, None, শূন্য (0, 0.0), আর খালি collection ("", [], (), {}, set())। বাকি প্রায় সব truthy — এমনকি "False" আর "0" string-ও। পর্ব ০৬

ভ-১২True + True কত? কেন? (মাঝারি)

2. bool হলো int-এর subclass — True হলো 1, False হলো 0; isinstance(True, int) True. এ কারণে sum(x > 0 for x in nums) দিয়ে ধনাত্মক সংখ্যা গোনা যায়। পর্ব ০৬

ভ-১৩Walrus operator := কী? (সহজ)

Python 3.8 থেকে আসা assignment expression — একটি expression-এর ভেতরে মান বাঁধে আর সেই মানই ফেরত দেয়: while (line := f.readline()):. একই হিসাব দুবার করা এড়ায়। পর্ব ০৮

ভ-১৪match কি C-এর switch-এর মতো? (মাঝারি)

শুধু নয় — Python 3.10 থেকে আসা match structural pattern matching: মানের পাশাপাশি আকার মেলায় (case [x, y]:, case {"type": "sms"}:, case Point(x=0):)। fall-through নেই, break লাগে না; case _: হলো default. পর্ব ১১

ভ-১৫লুপের else কখন চলে? (মাঝারি)

লুপ break ছাড়া স্বাভাবিকভাবে শেষ হলে। খোঁজার লুপে কাজে লাগে: পেলে break, না পেলে else-এ “পাওয়া যায়নি”. পর্ব ১২

ভ-১৬range(10) কি list? (সহজ)

না — Python 3-এ range একটি lazy sequence: শুরু, শেষ আর ধাপ মনে রাখে, মান চাইলে হিসাব করে। range(10**9)-ও প্রায় কোনো মেমরি নেয় না, আর len, index, in সবই চলে। পর্ব ১২

ভ-১৭string কি বদলানো যায়? len("বাংলা") কত? (মাঝারি)

না, str immutable — s[0] = "x" দেয় TypeError. len("বাংলা") → 5, কারণ len গোনে Unicode code point (ব, া, ং, ল, া), চোখে দেখা অক্ষর নয়। পর্ব ০৯

ভ-১৮f-string কী? (সহজ)

Python 3.6 থেকে f"..." — বন্ধনীর ভেতরে expression সরাসরি বসে: f"{price:.2f}", f"{n:,}" (1,234,567). f"{x=}" (3.8 থেকে) নাম আর মান দুটোই দেখায় — debug-এ কাজে লাগে। পর্ব ১০

ভ-১৯input() কোন টাইপ ফেরত দেয়? (সহজ)

সবসময় str — সংখ্যা চাইলে int(input()); ভুল লেখা দিলে ValueError, তাই try/except. মজার তথ্য: int("১২৩") কাজ করে → 123, কারণ int() Unicode অঙ্ক চেনে। পর্ব ১০

ভ-২০Python-এ ফাংশনে argument কীভাবে যায় — by value নাকি by reference? (কঠিন)

কোনোটিই হুবহু নয় — call by sharing (object reference by value): parameter একই object-এ নতুন নাম। ফাংশন mutable object বদলালে (lst.append) বাইরে দেখা যায়; parameter-কে নতুন object-এ বাঁধলে (lst = []) বাইরে কিছু বদলায় না। পর্ব ১৩

ভ-২১return না লিখলে ফাংশন কী ফেরত দেয়? (সহজ)

None. একই কারণে lst = lst.sort() একটি ক্লাসিক বাগ — sort() জায়গায় সাজায় আর None দেয়; নতুন list চাইলে sorted(lst). পর্ব ১৩

ভ-২২list কী? (সহজ)

ক্রম রাখা, বদলানো যায় এমন sequence, যেকোনো টাইপের object রাখে। CPython-এ ভেতরে object reference-এর একটি dynamic array — তাই index দিয়ে পড়া O(1). পর্ব ১৪

ভ-২৩ফলো-আপ: append কীভাবে প্রায় সবসময় O(1)? (মাঝারি)

list দরকারের চেয়ে কিছু বাড়তি জায়গা (capacity) আগেই নিয়ে রাখে; জায়গা ফুরালে বড় array-তে সব কপি করে — সেটি O(n), কিন্তু কদাচিৎ ঘটে, তাই গড়ে O(1) (amortized). insert(0, x) কিন্তু O(n). পর্ব ১৪

ভ-২৪ফলো-আপ: list mutable — তাহলে dict-এর key হতে পারে না কেন? (মাঝারি)

dict key-র hash দিয়ে ঘর ঠিক করে; key বদলালে hash বদলাত আর key হারিয়ে যেত। তাই list unhashable — {[1]: "x"} দেয় TypeError (বার্তায় থাকে unhashable type: 'list'). tuple বা frozenset key হতে পারে। পর্ব ১৬

ভ-২৫ফলো-আপ: {1: "a", 1.0: "b", True: "c"} — dict-এ কয়টি key? (কঠিন)

একটি — ফল {1: 'c'}. 1 == 1.0 == True আর তিনটির hash সমান, তাই একই key ধরা হয়; প্রথম key (1) থেকে যায়, মান শেষেরটি ('c'). পর্ব ১৬

ভ-২৬dict কি ক্রম রাখে? set? (সহজ)

dict insertion order রাখে — Python 3.7 থেকে এটি ভাষার নিশ্চয়তা। set-এর ক্রম নিশ্চিত নয়; string-এর set-এর ক্রম hash randomization-এর কারণে প্রতিবার চালানোয় বদলাতে পারে। পর্ব ১৭

ভ-২৭{} কী বানায় — খালি set নাকি dict? (সহজ)

খালি dict. খালি set বানাতে set(). {1, 2} অবশ্য set. পর্ব ১৭

ভ-২৮d[key] আর d.get(key)-এর পার্থক্য? (সহজ)

key না থাকলে d[key] দেয় KeyError, d.get(key) দেয় None (বা দেওয়া default: d.get(key, 0)). গোনার কাজে collections.Counter বা defaultdict(int) আরও পরিষ্কার। পর্ব ১৬

ভ-২৯(1) আর (1,)-এর পার্থক্য? (সহজ)

(1) শুধু বন্ধনীতে int 1; (1,) একটি উপাদানের tuple. tuple বানায় কমা, বন্ধনী নয় — t = 1, 2-ও tuple. পর্ব ১৫

ভ-৩০[[0] * 3] * 3 দিয়ে grid বানালে সমস্যা কী? (মাঝারি)

বাইরের * 3 একই ভেতরের list-এর তিনটি reference কপি করে — grid[0][0] = 1 করলে তিন সারিতেই 1. সঠিক: [[0] * 3 for _ in range(3)]. পর্ব ১৯

ভ-৩১নিচের কোড কেন UnboundLocalError দেয়? x = 10; ফাংশনে print(x), তারপর x = 5. (কঠিন)

ফাংশনের কোথাও x-এ assign থাকায় compile-এর সময়ই x পুরো ফাংশনে local ধরা হয়; print(x)-এর মুহূর্তে local x-এ এখনো মান নেই। global x বদলাতে চাইলে global x — তবে সাধারণত মান return করাই ভালো। পর্ব ২০

ভ-৩২[lambda: i for i in range(3)]-এর প্রতিটি ডাকলে কী পাবেন? (কঠিন)

তিনটিই 2 দেয়। lambda i-এর মান নয়, নামটিকে মনে রাখে, আর ডাকার সময় দেখে — তখন i-এর শেষ মান 2 (late binding). সমাধান: lambda i=i: i. পর্ব ২০

বড় প্রোগ্রাম গোছানো (পর্ব ২২–২৮)

রিভিশনের জন্য: পর্ব ২২, পর্ব ২৩, পর্ব ২৪, পর্ব ২৫, পর্ব ২৬, পর্ব ২৭, পর্ব ২৮.

ভ-৩৩একটি module দুবার import করলে কোড কয়বার চলে? (মাঝারি)

একবার। প্রথম import-এর পর module sys.modules-এ জমা থাকে; পরের import সেটিই ফেরত দেয়। জোর করে আবার চালাতে importlib.reload. পর্ব ২২

ভ-৩৪__init__.py-এর কাজ কী? (সহজ)

ফোল্ডারকে নিয়মিত package বানায়, আর package import করার সময় এটি চলে — সেখানে package-এর public নাম তুলে আনা যায়। Python 3.3 থেকে এটি ছাড়াও namespace package হয়, তবে সাধারণ প্রজেক্টে রাখাই প্রচলিত। পর্ব ২২

ভ-৩৫Circular import কী? সমাধান? (মাঝারি)

a.py b-কে import করে, b.py আবার a-কে — একটি পুরো লোড হওয়ার আগেই অন্যটি তার নাম চায়, ফলে ImportError বা অর্ধেক-লোড module. সমাধান: ভাগের কোড তৃতীয় module-এ সরানো, বা import ফাংশনের ভেতরে নেওয়া। পর্ব ২২

ভ-৩৬requirements.txt আর pyproject.toml-এর পার্থক্য? (মাঝারি)

requirements.txt হলো ইনস্টল করার package-এর তালিকা (প্রায়ই হুবহু সংস্করণসহ)। pyproject.toml প্রজেক্টের আধুনিক মূল কনফিগ — নাম, সংস্করণ, dependency, build system আর টুলের (ruff, pytest) সেটিং এক জায়গায়। পর্ব ২৩

ভ-৩৭খালি except: লেখা খারাপ কেন? (সহজ)

এটি সব ধরে — বানান ভুলের NameError, এমনকি Ctrl+C-র KeyboardInterrupt আর SystemExit-ও। বাগ লুকিয়ে যায়। নির্দিষ্ট exception ধরুন; একেবারে বাইরের স্তরে লগ রাখতে বড়জোর except Exception. পর্ব ২৪

ভ-৩৮try-এ return 1 আর finally-তে return 2 — ফাংশন কী ফেরত দেয়? (কঠিন)

2 — finally শেষে চলে আর তার return আগেরটিকে মুছে দেয়, এমনকি চলমান exception-ও গিলে ফেলে। এ জন্য Python 3.14 থেকে finally-র ভেতরে return/break/continue দেখলে SyntaxWarning দেয় (PEP 765). পর্ব ২৪

ভ-৩৯raise আর raise ... from err-এর পার্থক্য? (মাঝারি)

except-এর ভেতরে খালি raise ধরা exception-টিই আবার ছোড়ে। raise ConfigError(...) from err নতুন exception ছোড়ে আর মূল কারণ __cause__-এ যুক্ত রাখে — traceback-এ দুটোই দেখা যায় (exception chaining). পর্ব ২৪

ভ-৪০ফাইল "w" mode-এ খুললে আগের লেখার কী হয়? (সহজ)

খোলার মুহূর্তেই মুছে যায় (truncate). শেষে যোগ করতে "a", আর ফাইল আগে থাকলে ভুল দিতে "x" (FileExistsError). পর্ব ২৫

ভ-৪১json.dumps-এ বাংলা \u09ac\u09be... হয়ে যায় কেন? (মাঝারি)

ডিফল্টে ensure_ascii=True, তাই ASCII-র বাইরের অক্ষর escape হয়। পড়ার মতো বাংলা চাইলে json.dumps(data, ensure_ascii=False), আর ফাইলে লিখলে encoding="utf-8". পর্ব ২৬

ভ-৪২JSON-এ কি tuple বা set রাখা যায়? (মাঝারি)

tuple লেখা যায়, কিন্তু JSON array হয়ে যায় — পড়লে ফেরত আসে list. set সরাসরি TypeError দেয়; আগে list-এ রূপান্তর করতে হয়। JSON-এর key সবসময় string — int key পড়লে "1" হয়ে ফেরে। পর্ব ২৬

ভ-৪৩OTP বানাতে random নাকি secrets? (সহজ)

secrets — এটি নিরাপত্তার জন্য বানানো (OS-এর CSPRNG). random Mersenne Twister-ভিত্তিক, যথেষ্ট আউটপুট দেখলে পরের মান অনুমান করা যায়; simulation আর খেলার জন্য ঠিক আছে। পর্ব ২৭

ভ-৪৪re.match আর re.search-এর পার্থক্য? (সহজ)

match শুধু string-এর শুরু থেকে মেলায়; search যেকোনো জায়গায় প্রথম মিল খোঁজে। পুরো string মেলাতে fullmatch. মিল না পেলে তিনটিই None দেয়। পর্ব ২৮

অবজেক্ট-ওরিয়েন্টেড Python (পর্ব ২৯–৩৫)

রিভিশনের জন্য: পর্ব ২৯, পর্ব ৩০, পর্ব ৩১, পর্ব ৩২, পর্ব ৩৩, পর্ব ৩৪, পর্ব ৩৫.

ভ-৪৫Class আর object-এর পার্থক্য এক লাইনে? (সহজ)

Class হলো নকশা (ছাঁচ), object হলো সেই নকশা থেকে বানানো একটি নির্দিষ্ট জিনিস — Account class, Account("রহিম") object. Python-এ class নিজেও একটি object (টাইপ type). পর্ব ২৯

ভ-৪৬__init__ কি constructor? (মাঝারি)

ঠিক নয় — object বানায় __new__, আর __init__ বানানো object-এর শুরুর মান বসায় (initializer). দৈনন্দিন কাজে __init__-কেই constructor বলা হয়; __new__ লাগে immutable টাইপ subclass করার মতো বিরল কাজে। পর্ব ২৯

ভ-৪৭self কি keyword? (সহজ)

না — শুধু প্রথার নাম। method-এর প্রথম parameter-এ Python ডাকার object পাঠায়: acc.deposit(100) = Account.deposit(acc, 100). অন্য নাম দিলেও চলবে, কিন্তু PEP 8 আর সবার অভ্যাস self. পর্ব ২৯

ভ-৪৮@classmethod আর @staticmethod-এর পার্থক্য? (মাঝারি)

classmethod প্রথম argument হিসেবে class পায় (cls) — বিকল্প constructor-এর জন্য, যেমন Date.from_string("2026-10-18"), subclass-এও ঠিক class বানায়। staticmethod কিছুই পায় না — শুধু class-এর নামের ভেতরে রাখা সাধারণ ফাংশন। পর্ব ২৯

ভ-৪৯Python-এ কি private attribute আছে? (মাঝারি)

কড়া অর্থে নেই। _x প্রথা — “ভেতরের, ছোঁবেন না”. __x name mangling করে _Class__x বানায় — subclass-এর নামের সংঘাত এড়াতে, লুকাতে নয়; চাইলে এখনো পড়া যায়। পর্ব ২৯

ভ-৫০__str__ আর __repr__-এর পার্থক্য? (সহজ)

__str__ ব্যবহারকারীর জন্য সুন্দর লেখা (print); __repr__ developer-এর জন্য স্পষ্ট রূপ (REPL, debugger, list-এর ভেতরে)। শুধু একটি লিখলে __repr__ লিখুন — str() না পেলে সেটিই নেয়। পর্ব ৩০

ভ-৫১নিজের class-কে len() আর for-এ চালাতে কী লাগবে? (মাঝারি)

len()-এর জন্য __len__; for-এর জন্য __iter__ (বা পুরোনো পথে ০ থেকে index নেওয়া __getitem__)। in-এর জন্য __contains__, না থাকলে Python iterate করে খোঁজে। পর্ব ৩০

ভ-৫২__eq__ লিখলে object dict-এর key হতে পারে না কেন? (কঠিন)

__eq__ সংজ্ঞায়িত করলে আর __hash__ না দিলে Python __hash__ = None করে দেয় — কারণ নিয়ম হলো “সমান object-এর hash সমান হতেই হবে”, আর পুরোনো id-ভিত্তিক hash সেটা ভাঙত। সমাধান: অপরিবর্তনীয় field থেকে __hash__ লেখা, বা @dataclass(frozen=True). পর্ব ৩০

ভ-৫৩super() কি সবসময় parent class-কে ডাকে? (কঠিন)

না — ডাকে object-এর আসল class-এর MRO-তে বর্তমান class-এর পরের class-কে। diamond-এ Left-এর super() তাই Right-এ যেতে পারে, যদিও Right তার parent নয় (গ-০৪)। পর্ব ৩১

ভ-৫৪Composition কেন প্রায়ই inheritance-এর চেয়ে ভালো? (মাঝারি)

inheritance শক্ত “is-a” বাঁধন — base বদলালে সব subclass কাঁপে। composition (“has-a”): Car-এর ভেতরে একটি Engine object — অংশ বদলানো, test করা আর চলার সময় পাল্টানো সহজ। পর্ব ৩১

ভ-৫৫__slots__ কী কাজে লাগে? (কঠিন)

class-এ নির্দিষ্ট attribute-এর তালিকা দিলে প্রতিটি object-এ __dict__ থাকে না — লাখো object হলে মেমরি অনেক বাঁচে, attribute পড়াও একটু দ্রুত। দাম: তালিকার বাইরে নতুন attribute যোগ করা যায় না (AttributeError). পর্ব ২৯

ভ-৫৬Abstract class-এর object বানানো যায়? (সহজ)

না — ABC থেকে বানানো class-এ কোনো @abstractmethod বাস্তবায়িত না থাকলে object বানাতে গেলেই TypeError: Can't instantiate abstract class .... সব abstract method বাস্তবায়ন করা subclass-এর object বানানো যায়। পর্ব ৩৩

ভ-৫৭Iterable আর iterator-এর পার্থক্য? (মাঝারি)

iterable থেকে iterator পাওয়া যায় (iter(x)) — list, str. iterator নিজে একটি একটি মান দেয় (next()) আর একবারই শেষ হয়। list-এর ওপর দুবার লুপ চলে; একটি iterator-এর ওপর দ্বিতীয় লুপ কিছুই পায় না। পর্ব ৩৪

ভ-৫৮ফলো-আপ: generator ফাংশন ডাকলে সাথে সাথে কী ঘটে? (মাঝারি)

শরীরের একটি লাইনও চলে না — শুধু একটি generator object ফেরত আসে। প্রথম next()-এ চলা শুরু হয়, প্রথম yield-এ থামে। পর্ব ৩৪

ভ-৫৯ফলো-আপ: return আর yield-এর পার্থক্য? (সহজ)

return ফাংশন শেষ করে একটি মান দেয়; yield একটি মান দিয়ে থেমে থাকে, অবস্থা মনে রাখে, আর পরের next()-এ সেখান থেকে চলে। generator-এ return মানে iteration শেষ। পর্ব ৩৪

আধুনিক Python ও বাস্তব প্রয়োগ (পর্ব ৩৬–৫১)

রিভিশনের জন্য: পর্ব ৩৬, পর্ব ৩৭, পর্ব ৩৮, পর্ব ৩৯, পর্ব ৪০, পর্ব ৪১, পর্ব ৪২, পর্ব ৪৩, পর্ব ৪৫, পর্ব ৪৬, পর্ব ৪৭, পর্ব ৪৮, পর্ব ৫০.

ভ-৬০with block-এ exception হলে ফাইল কি বন্ধ হয়? (সহজ)

হ্যাঁ — __exit__ সবসময় চলে, তাই ফাইল বন্ধ হয়; তারপর exception স্বাভাবিকভাবে ওপরে যায় (যদি না __exit__ True ফেরত দেয়)। পর্ব ৩৬

ভ-৬১Type hint ভুল দিলে কি Python প্রোগ্রাম থামায়? (সহজ)

না — interpreter hint যাচাই করে না। ভুল ধরে mypy বা pyright-এর মতো static checker, চালানোর আগে। চলার সময় যাচাই চাইলে pydantic. পর্ব ৩৭

ভ-৬২lambda কী? কখন ব্যবহার করবেন না? (সহজ)

এক expression-এর নামহীন ফাংশন: sorted(people, key=lambda p: p.age). নাম দিয়ে রাখার জন্য (f = lambda x: ...) নয় — তখন def লিখুন, traceback-এ নাম দেখা যায়, PEP 8-ও তাই বলে। পর্ব ৩৮

ভ-৬৩functools.lru_cache কী করে? (মাঝারি)

ফাংশনের আগের ফল argument অনুযায়ী মনে রাখে (memoization) — একই argument-এ আবার হিসাব হয় না; recursive Fibonacci সূচকীয় থেকে রৈখিক সময়ে নামে। argument hashable হতে হবে, আর ফাংশন pure হওয়া উচিত। পর্ব ৩৮

ভ-৬৪pytest-এ fixture কী? (সহজ)

test-এর প্রস্তুতি (আর yield দিয়ে পরিষ্কার) দেওয়ার ফাংশন; test parameter-এর নাম মিলিয়ে pytest নিজে দেয়। tmp_path, capsys, monkeypatch তৈরি fixture. পর্ব ৩৯

ভ-৬৫print দিয়ে debug না করে কী ব্যবহার করবেন? (সহজ)

ধাপে ধাপে দেখতে debugger — কোডে breakpoint() লিখলে pdb খোলে, বা VS Code-এর debugger. প্রোডাকশনের তথ্য রাখতে logging module — level (DEBUG, INFO, WARNING, ERROR) আর সময়সহ, কোড না মুছেই বন্ধ করা যায়। পর্ব ৪০

ভ-৬৬GIL কী, এক বাক্যে? (সহজ)

CPython-এর একটি lock, যার কারণে এক process-এ একসাথে একটিই thread Python bytecode চালায় — তাই CPU-ভারী কাজে thread দিয়ে গতি বাড়ে না। পর্ব ৪১

ভ-৬৭ফলো-আপ: তাহলে Python-এ thread একেবারেই কাজের না? (মাঝারি)

কাজের — নেটওয়ার্ক, ফাইল বা sleep-এর অপেক্ষায় thread GIL ছেড়ে দেয়, তাই I/O-ভারী কাজ thread-এ অনেক দ্রুত হয়। CPU-ভারী কাজে multiprocessing, বা GIL ছেড়ে দেওয়া C লাইব্রেরি। পর্ব ৪১

ভ-৬৮ফলো-আপ: GIL কি কখনো সরবে? (কঠিন)

পথে আছে: PEP 703-এর free-threaded CPython 3.13-এ পরীক্ষামূলক ছিল, 3.14-এ আনুষ্ঠানিকভাবে সমর্থিত — তবে এখনো আলাদা build (যেমন python3.14t), ডিফল্ট নয়, আর কিছু C extension এখনো মানিয়ে নিচ্ছে। কবে ডিফল্ট হবে, তা নির্দিষ্ট নয়। পর্ব ৪১

ভ-৬৯ফলো-আপ: GIL থাকলে কি counter += 1 thread-safe? (কঠিন)

না। += কয়েকটি ধাপে ভাঙে (পড়া, যোগ, লেখা), আর মাঝখানে thread বদলাতে পারে — update হারায় (race condition). threading.Lock দিয়ে মুড়তে হবে। পর্ব ৪১

ভ-৭০asyncio-তে time.sleep(1) লিখলে সমস্যা কী? (মাঝারি)

এটি blocking — পুরো event loop এক সেকেন্ড আটকে থাকে, অন্য কোনো coroutine চলে না। লিখতে হবে await asyncio.sleep(1); blocking লাইব্রেরি ডাকতে asyncio.to_thread. পর্ব ৪২

ভ-৭১কোড কোথায় ধীর, কীভাবে জানবেন? (সহজ)

অনুমান নয়, মাপা: python -m cProfile -s cumtime app.py পুরো প্রোগ্রামের কোন ফাংশনে কত সময় দেখায়; ছোট টুকরোর তুলনায় timeit. তারপর আগে অ্যালগরিদম আর ডেটা স্ট্রাকচার ঠিক করুন। পর্ব ৪৩

ভ-৭২NumPy-র হিসাব সাধারণ Python লুপের চেয়ে দ্রুত কেন? (মাঝারি)

ndarray একই টাইপের সংখ্যা পাশাপাশি মেমরিতে রাখে, আর পুরো array-র হিসাব একবারে C-তে লেখা লুপে চলে (vectorization) — প্রতিটি উপাদানে Python object আর interpreter-এর খরচ নেই। পর্ব ৪৬

ভ-৭৩HTTP-তে GET আর POST-এর পার্থক্য? (সহজ)

GET ডেটা চায়, প্যারামিটার URL-এ যায়, বারবার পাঠালেও সার্ভারের অবস্থা বদলানোর কথা নয়। POST নতুন কিছু তৈরি বা পাঠায়, ডেটা যায় request body-তে। password কখনো URL-এ নয়। পর্ব ৪৭

ভ-৭৪API key কোডে লিখে GitHub-এ দিলে কী করবেন? (মাঝারি)

সাথে সাথে key বাতিল করে নতুন key বানান (rotate) — commit মুছলেও git history আর অন্যের কপিতে থেকে যায়। এরপর key রাখুন environment variable বা .env-এ (.gitignore-এ যোগ করে)। পর্ব ৫০

৯ · AI-কে ভাইভা সঙ্গী বানান — আর তাকে যাচাই করুন

ভাইভা অনুশীলনের সবচেয়ে বড় সমস্যা হলো প্রশ্ন করার মানুষ না থাকা। AI এখানে কাজের সঙ্গী — ক্লান্ত হয় না, যেকোনো সময় প্রস্তুত, আর আপনার উত্তর থেকে follow-up প্রশ্ন বানাতে পারে। কিন্তু নিজে থেকে AI প্রায়ই খুব নরম পরীক্ষক — সব উত্তরেই “চমৎকার!” বলে। আর Python-এর সূক্ষ্ম বিষয়ে (কোন সংস্করণে কী এসেছে, CPython-এর বাস্তবায়ন বনাম ভাষার নিয়ম, GIL-এর বর্তমান অবস্থা) AI-ও আত্মবিশ্বাসের সাথে পুরোনো বা ভুল তথ্য দিতে পারে। তাই তাকে স্পষ্ট নিয়ম দিন, আর প্রতিটি দাবি যাচাই করুন।

নিচের prompt-টি কপি করে যেকোনো AI চ্যাটে দিন। বিষয়ের জায়গায় যে মডিউল ঝালাতে চান সেটি লিখুন:

তুমি একজন কড়া Python ভাইভা পরীক্ষক। বিষয়: [mutability, copy আর scope/closure]. ধরে নাও CPython 3.14. নিয়ম: ১. একবারে একটিই প্রশ্ন করো, তারপর আমার উত্তরের জন্য অপেক্ষা করো। ২. আমার উত্তর থেকেই আরও গভীর একটি follow-up প্রশ্ন বানাও — যেমন সত্যিকারের ভাইভা বোর্ড করে। ৩. প্রতিটি উত্তরকে ১০-এর মধ্যে নম্বর দাও, আর ঠিক কোন তথ্য ভুল বা বাদ পড়েছে বলো। প্রশংসা নয়, সঠিকতা। ৪. আমি ভুল বললে সাথে সাথে সঠিক উত্তর দিও না — আগে একটা ইঙ্গিত দাও, আরেকবার চেষ্টা করতে দাও। ৫. যেখানে উত্তর CPython-এর বাস্তবায়নের ওপর নির্ভর করে (small int cache, GIL), সেখানে আমি সেটা আলাদা করে বলছি কি না দেখো। ৬. কোনো তথ্যে তুমি নিশ্চিত না হলে স্পষ্ট বলো; কোন Python সংস্করণে কোন বৈশিষ্ট্য এসেছে, তা আন্দাজে বলো না। ৭. ১০টি প্রশ্নের পর আমার দুর্বল জায়গার তালিকা দাও, আর কোন ধারণা আবার পড়া উচিত বলো।

লিখিত উত্তরের জন্য খাতার উত্তর টাইপ করে দিন এবং বলুন: “এটি একটি বড় প্রশ্নের উত্তর। পরীক্ষক হিসেবে নম্বর দাও, আর সংজ্ঞা, ব্যাখ্যা, কোড, চিত্র, উপসংহার — কোন অংশ দুর্বল বলো; কিন্তু পুরো উত্তর নতুন করে লিখে দিও না।” শেষ শর্তটি জরুরি — নইলে আপনি শিখবেন না, শুধু আরেকটি উত্তর মুখস্থ করবেন।

Python নিয়ে AI-এর উত্তরে যেসব ভুল প্রায়ই থাকে

AI যা বলে বা লেখেআসল সত্য
“Python পুরোপুরি interpreted, কোনো compile নেই”CPython আগে bytecode-এ compile করে (ক-০১)
“Python-এ argument pass by reference”call by sharing; parameter-কে নতুন object-এ বাঁধলে বাইরে বদলায় না (ক-০২)
“tuple immutable, তাই সবসময় hashable”ভেতরে list থাকলে unhashable (অংশ ৩-এর শিকল)
“GIL আছে বলে Python কোড thread-safe”+=-এর মতো কাজেও race হয়; lock লাগে (ভ-৬৯)
“Python-এ GIL সরানো সম্ভব নয়” বা “3.14-এ GIL নেই”free-threaded build 3.14-এ সমর্থিত, কিন্তু ডিফল্ট নয় (ঘ-০৩)
কোডে def f(x, items=[]), encoding ছাড়া open()None default; encoding="utf-8" (ক-০৮, খ-০৪)
“type hint ভুল হলে Python error দেয়”interpreter যাচাই করে না; mypy করে (ঘ-০২)

AI-এর উত্তর যাচাইয়ের চেকলিস্ট

নিজেকে যাচাই করুন — উত্তর লেখা ও Python-এর মূল তথ্য

প্রথমে একটি ক্রম সাজানোর প্রশ্ন, তারপর উত্তর লেখার কৌশল আর Python-এর ক্লাসিক ভাইভা তথ্য নিয়ে MCQ. ভুল হলে কারণসহ ব্যাখ্যা পাবেন।

ক্রম“উদাহরণসহ ব্যাখ্যা করুন” প্রশ্নের একটি ভালো লিখিত উত্তরের অংশগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📌 এক–দুই বাক্যের নির্ভুল সংজ্ঞা
  2. 🧩 ব্যাখ্যা — কীভাবে কাজ করে, কেন এভাবে
  3. 💻 ছোট উদাহরণ কোড ও তার আউটপুট
  4. 🗺️ চিত্র বা তুলনার টেবিল
  5. ✅ উপসংহার — সতর্কতা, খরচ, কখন ব্যবহার

“Python-এ decorator কীভাবে কাজ করে? উদাহরণসহ লিখুন” — একটি বড় প্রশ্নে কোন উত্তরটি পূর্ণ নম্বরের সবচেয়ে কাছে?

  • “ফাংশনের ওপরে @ দিয়ে লেখা জিনিস” — এক লাইন
  • দুই পাতা জুড়ে Python-এর জন্ম থেকে আজ পর্যন্ত ইতিহাস
  • সংজ্ঞা, “@d মানে f = d(f)” ব্যাখ্যা, wraps-সহ ছোট কোড ও আউটপুট, নামের তীর-চিত্র, আর ব্যবহার ও সতর্কতা
  • শুধু একটি ৪০ লাইনের প্রোগ্রাম, কোনো লেখা ছাড়া

ভাইভায় এমন প্রশ্ন এলো, যার উত্তর আপনি পুরোপুরি জানেন না। সবচেয়ে ভালো পদক্ষেপ কোনটি?

  • আত্মবিশ্বাস দেখাতে আন্দাজে একটি উত্তর জোর দিয়ে বলা
  • “পুরো নিশ্চিত নই, তবে এভাবে ভাবব…” বলে যা জানেন তা থেকে যুক্তি দিয়ে এগোনো
  • চুপ করে থাকা, যতক্ষণ না পরের প্রশ্ন আসে
  • প্রশ্ন এড়িয়ে নিজের জানা অন্য বিষয়ে কথা বলা শুরু করা

Python-এ নিচের কোনটি dict-এর key হিসেবে ব্যবহার করা যায় না?

  • একটি str, যেমন "dhaka"
  • একটি tuple, যেমন (23, 90)
  • একটি frozenset, যেমন frozenset({1, 2})
  • একটি list, যেমন [23, 90]

def add(x, items=[]): items.append(x); return items — পরপর add(1) আর add(2) ডাকলে দ্বিতীয় ডাক কী ফেরত দেয়?

  • [1, 2]
  • [2]
  • TypeError
  • None

CPython-এ GIL সম্পর্কে কোন কথাটি সঠিক?

  • GIL থাকায় CPU-ভারী কাজ বেশি thread দিলে সমানুপাতিক হারে দ্রুত হয়
  • GIL থাকায় নিজের কোডে কখনো lock লাগে না
  • নেটওয়ার্ক বা ফাইলের অপেক্ষার সময় thread GIL ছেড়ে দেয়, তাই I/O-ভারী কাজে thread কাজে লাগে
  • GIL Python ভাষার নিয়ম, তাই সব বাস্তবায়নে আছে

class Child(Left, Right), যেখানে Left আর Right দুটোই Base থেকে আসে, আর সব class super() ব্যবহার করে। Child-এর MRO কী?

  • Child → Left → Base → Right → Base → object
  • Child → Left → Right → Base → object
  • Child → Right → Left → Base → object
  • TypeError — diamond-এ MRO বানানো যায় না

একটি generator ফাংশন gen()-এর শরীরে প্রথম লাইনে print("start") আছে। g = gen() লিখলে কী ঘটে?

  • সাথে সাথে start ছাপা হয়
  • প্রথম yield পর্যন্ত চলে থামে
  • StopIteration ছোড়ে
  • শুধু একটি generator object ফেরত আসে; শরীরের কিছুই চলে না

লিখিত পরীক্ষায় একটি ছোট প্রশ্ন: “context manager কী?” — কোন উত্তরটি সবচেয়ে উপযুক্ত?

  • যে object with-এর আগে প্রস্তুতি (__enter__) আর পরে পরিষ্কার (__exit__) নিশ্চিত করে, exception হলেও। উদাহরণ: with open(...) as f.
  • তিন পাতা — contextlib-এর সব ফাংশন, চারটি পূর্ণ প্রোগ্রাম আর C++-এর সাথে তুলনা
  • “with keyword” — শুধু এটুকু
  • “Python-এর garbage collector, যা অব্যবহৃত মেমরি মুছে দেয়”

শব্দকোষ

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

মনে রাখুন

  1. বড় প্রশ্নে পাঁচ অংশ: সংজ্ঞা → ব্যাখ্যা → উদাহরণ কোড → চিত্র বা টেবিল → উপসংহার; ছোট প্রশ্নে সংজ্ঞা, এক লাইনের ব্যাখ্যা আর একটি উদাহরণ।
  2. “পার্থক্য লিখুন” প্রশ্নে টেবিল — একই মাপকাঠিতে সারি, complexity, আর “কখন কোনটি” সারি।
  3. খাতার কোডে ঠিক indentation আর আধুনিক Python: f-string, with, encoding="utf-8", PEP 8 নাম — আর নিচে আউটপুট।
  4. ভাইভায় আগে ছোট নির্ভুল উত্তর, তারপর উদাহরণ; ভাষার নিয়ম আর CPython-এর বাস্তবায়ন আলাদা করুন, সংস্করণ বলুন, না জানলে সৎভাবে ভেবে বলুন।
  5. AI দিয়ে ভাইভা অনুশীলন করুন, কিন্তু তার প্রতিটি কোড চালিয়ে আর প্রতিটি দাবি মিলিয়ে নিন।

পরের পর্বে: লিখিত আর মৌখিক প্রশ্নের পর শেষ ধাপ — Python কোডিং ইন্টারভিউ। অচেনা সমস্যা থেকে প্রশ্ন করে শর্ত বোঝা, brute force থেকে ভালো সমাধানে যাওয়া, আর ইন্টারভিউয়ারের সামনে জোরে ভেবে পরিষ্কার Python লেখা।

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

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