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

MCQ প্রশ্নব্যাংক — Python-এর ৯০টি প্রশ্নে যেকোনো পরীক্ষার প্রস্তুতি

Python MCQ Question Bank — 90 Questions, Every Option Explained

Taught in Bangla, this question bank has 90 Python MCQs in 8 sections covering the whole course, from interpreter basics, types and strings to data structures, modules and exceptions, OOP, modern Python and real-world topics. Every option is explained, answers are verified on CPython 3.14, and each section points to the lessons to revise.

এই পর্বে যা করবেন

  • MCQ পরীক্ষা Python-এর কোন কোন দিক যাচাই করে, তা চেনা
  • বাদ দেওয়ার পদ্ধতি, ভুলে নম্বর কাটার হিসাব আর সময় ভাগের কৌশল শেখা
  • পুরো কোর্সের ৯০টি প্রশ্নে নিজের দুর্বল জায়গা খুঁজে বের করা
  • সবচেয়ে প্রচলিত ফাঁদ — list বনাম tuple, is বনাম ==, mutable default, shallow বনাম deep copy, GIL, generator — গভীরভাবে বোঝা
  • AI দিয়ে আরও প্রশ্ন বানানো, আর সেগুলোর “সঠিক উত্তর” চালিয়ে যাচাই করা

১ · MCQ পরীক্ষা Python-কে কীভাবে যাচাই করে — আর উত্তর বাছাইয়ের কৌশল

তানিয়া একটি সফটওয়্যার কোম্পানির অনলাইন পরীক্ষার জন্য পড়ছেন। একই মাসে একটি প্রতিষ্ঠানের প্রোগ্রামার পদের লিখিত পরীক্ষাও আছে। দুই জায়গাতেই প্রথম ধাপ MCQ — চারটি অপশন, একটি সঠিক। তানিয়া Python-এ ডেটা পরিষ্কারের স্ক্রিপ্ট লিখেছেন, ছোট একটি FastAPI প্রজেক্টও করেছেন। তবু প্রথম অনুশীলন সেটে ৪০টির মধ্যে ঠিক হলো ২৫টি। কেন?

কারণ MCQ প্রোগ্রাম লেখার পরীক্ষা নয়। এটি নিখুঁত খুঁটিনাটির পরীক্ষা। কোড লেখার সময় interpreter ভুল দেখিয়ে দেয়, আপনি ঠিক করে নেন। MCQ-তে কোনো interpreter নেই — আপনার মাথাই interpreter. আর প্রশ্নকর্তা জেনেশুনে এমন ভুল অপশন রাখেন, যা দেখতে একদম ঠিক মনে হয়। এমন বিভ্রান্তিকর অপশনকে বলে distractor. Python-এ distractor বানানো সহজ, কারণ ভাষাটি পড়তে ইংরেজির মতো — আর সেই “স্বাভাবিক” পড়াটাই অনেক সময় ভুল পথে নেয়।

আট ধরনের প্রশ্ন

Python-এর MCQ মোটামুটি আটটি ছাঁচে পড়ে। কোন ছাঁচের প্রশ্ন, সেটা চিনতে পারলে অর্ধেক কাজ হয়ে যায়:

ধরনউদাহরণআসলে কী মাপে
ধারণা মনে রাখা“bytecode কী?”সংজ্ঞা সঠিকভাবে জানেন কি না
বৈধ কোনটি?“কোন লাইনে SyntaxError?”ব্যাকরণ, keyword আর indentation-এর নিয়ম
টাইপ ও mutability“কোনটি dict-এর key হতে পারে না?”কোন object বদলায়, কোনটি hashable
আউটপুট কী?দুই-তিন লাইনের কোড, চারটি আউটপুটমাথায় কোড চালানো — alias, scope, generator
কোনটি exception দেবে?“কোন লাইনটি TypeError দেবে?”exception-এর নাম আর কখন ওঠে
কমপ্লেক্সিটি“list-এ x in-এর সময়?”কোন ডেটা স্ট্রাকচার কোন কাজে কত দ্রুত
কোন module কোন কাজে“শব্দ গুনতে কোনটি?”স্ট্যান্ডার্ড লাইব্রেরির পরিচয়
আধুনিক Python“match কোন সংস্করণে এলো?”3.8 থেকে 3.14-এর নতুন সুবিধা

কৌশল ১ · বাদ দেওয়ার পদ্ধতি

সঠিক উত্তর সরাসরি খোঁজার চেয়ে ভুলগুলো বাদ দেওয়া প্রায়ই সহজ। একে বলে elimination method. প্রতিটি অপশনকে আলাদা করে জিজ্ঞেস করুন: “এটি ঠিক হতে হলে কোন নিয়মটি সত্য হতে হবে?” যে অপশনের পেছনের নিয়মটি মিথ্যা, সেটি কেটে দিন। নিচের ডায়াগ্রামে একটি চতুর প্রশ্ন এভাবে ধাপে ধাপে সমাধান করা হয়েছে।

  1. বিষয় চেনাb = aalias
  2. বৈধ কি?হ্যাঁগ বাদ
  3. কী ছাপে?listঘ বাদ
  4. += কি নতুন?নাক বাদ
  5. যাচাই[1, 2, 3, 4]উত্তর খ
প্রশ্নPython 3.14
a = [1, 2, 3]
b = a
a += [4]
print(b)

ক) [1, 2, 3]   খ) [1, 2, 3, 4]
গ) TypeError   ঘ) None
ধাপ ২কোডটি কি চলবে?
list + list  → বৈধ
list += list → বৈধ (দুটিই list)

ক) [1, 2, 3]   খ) [1, 2, 3, 4]
গ) TypeError   ঘ) None
ধাপ ৩print(b) কী দেখায়?
b একটি list-কে দেখায়
print(b) → সেই list ছাপে
None আসত print(a.append(4))-এ

ক) [1, 2, 3]   খ) [1, 2, 3, 4]
গ) TypeError   ঘ) None
ধাপ ৪+= নতুন list বানায়?
a, b ──► [1, 2, 3, 4]   (একই object)
list-এর += মানে extend, জায়গায় বদল

ক) [1, 2, 3]   খ) [1, 2, 3, 4]
গ) TypeError   ঘ) None
ধাপ ৫উত্তর ও ফাঁদের চেক
উত্তর: খ) [1, 2, 3, 4]

যদি লেখা থাকত  a = a + [4]
তাহলে a নতুন list → b আগেরটিই
→ উত্তর হতো ক) [1, 2, 3]
  1. আগে বিষয় চিনুন। কোডে কোনো লুপ বা হিসাব নেই — আছে b = a আর তারপর a-কে বদলানো। তাই এটি “alias আর mutable object” ধরনের প্রশ্ন। মাথায় প্রশ্ন আনুন: a আর b কি একই object, আর += কি সেই object-কেই বদলায়?
  2. কোডটি কি আদৌ চলবে? list-এর সাথে list যোগ করা বৈধ, +=-ও বৈধ। TypeError আসত যদি list-এর সাথে একটি int যোগ করা হতো, যেমন a += 4. তাই গ বাদ। “Error” অপশনটি সব সময় আগে যাচাই করুন, কারণ প্রশ্নকর্তা প্রায়ই সঠিক উত্তর হিসেবেই এমন অপশন রাখেন।
  3. print কী ছাপে? print(b) b-এর মান ছাপে — একটি list. None ছাপা হতো যদি লেখা থাকত print(a.append(4)), কারণ append কিছু ফেরত দেয় না। এই অপশনটি সেই ভুল ধারণা ধরতে রাখা। তাই ঘ বাদ।
  4. += কি নতুন list বানায়? না। list-এর ক্ষেত্রে a += [4] ভেতরে a.extend([4])-এর মতো কাজ করে — একই object জায়গায় বদলায়। b সেই একই object দেখায়, তাই b-তেও 4 দেখা যায়। ক বাদ। এখন বাকি একটিই।
  5. উত্তর খ — তারপর ফাঁদটি মনে রাখুন। শেষে ভাবুন, প্রশ্নকর্তা ক কেন রেখেছিলেন। a = a + [4] লিখলে ডান পাশে একটি নতুন list তৈরি হতো, আর a সেই নতুনটিতে চলে যেত; b থেকে যেত পুরোনোটিতে — উত্তর হতো ক. ভুল অপশন দেখেই বোঝা যায় প্রশ্নটি কোন ভুল ধারণা ধরতে চায়।

কৌশল ২ · “Error” আর “Exception” অপশন

Python-এর MCQ-তে প্রায়ই একটি অপশন থাকে TypeError, SyntaxError বা শুধু “Error”. অনেকে এগুলো “ফাঁকা ঘর” ভেবে এড়িয়ে যান। ভুল। এগুলো প্রায়ই সঠিক উত্তর। তিনটি জিনিস আলাদা রাখুন:

SyntaxError

কোডটি ভাষার ব্যাকরণ ভাঙে, তাই bytecode-এ রূপান্তরই হয় না। ফাইলের কোথাও একটি SyntaxError থাকলে প্রথম লাইনটিও চলে না — আগের print-ও কিছু ছাপে না। IndentationError এরই একটি উপশ্রেণি।

কিছুই চলে না

Runtime exception

কোড চলতে শুরু করে; যে লাইনে সমস্যা, সেখানে exception ওঠে। আগের লাইনগুলোর আউটপুট ছাপা হয়ে গেছে। TypeError, KeyError, ValueError, UnboundLocalError — নামটি নিখুঁত জানতে হয়।

আগের লাইন চলে, তারপর থামে

Warning

প্রোগ্রাম থামে না, শুধু stderr-এ সতর্কবার্তা আসে। যেমন x is 5 লিখলে SyntaxWarning, বা await না করা coroutine-এ RuntimeWarning. ফল স্বাভাবিকভাবেই ছাপা হয়।

ফল আসে, সাথে সতর্কতা
মূল ধারণা

প্রশ্নে কোড থাকলে আউটপুট খোঁজার আগে দুটি প্রশ্ন করুন। এক, “এটি কি বৈধ Python?” — indentation, :=-এর জায়গা, match-এর case-এর ক্রম, Python 2-এর লেখা। দুই, “চললে কোনো লাইনে exception উঠবে কি?” — str আর int যোগ, list-কে dict-এর key, না থাকা key, assignment-এর আগে local পড়া। দুটির কোনোটির উত্তর “হ্যাঁ” হলে মান নিয়ে মাথা ঘামানোর দরকার নেই। আর মনে রাখুন, exception-এর শ্রেণিবিন্যাস আছে: UnboundLocalError হলো NameError-এর উপশ্রেণি, KeyError হলো LookupError-এর — অপশনে দুটিই থাকলে সবচেয়ে নির্দিষ্ট আর বার্তাসহ মেলে যেটি, সেটিই উত্তর।

কৌশল ৩ · ভুলে নম্বর কাটলে কখন আন্দাজ করবেন

যেসব পরীক্ষায় ভুল উত্তরে নম্বর কাটে (negative marking), সেখানে আন্দাজ করা একটি হিসাবের প্রশ্ন। নিয়ম পরীক্ষাভেদে আলাদা, তাই নিজের পরীক্ষার নিয়ম আগে জেনে নিন। এখানে শুধু হিসাবের পদ্ধতিটি দেখাচ্ছি। ধরুন সঠিক উত্তরে ১ নম্বর। প্রতি প্রশ্নে গড়ে কত পাবেন:

বাকি অপশনভুলে ০.২৫ কাটলেভুলে ০.৫ কাটলে
৪টি (কিছুই জানেন না)+০.০৬−০.১২৫ (ক্ষতি)
৩টি (একটি বাদ দিয়েছেন)+০.১৭০ (লাভ-ক্ষতি নেই)
২টি (দুটি বাদ দিয়েছেন)+০.৩৭৫+০.২৫

হিসাবটি সহজ: (ঠিক হওয়ার সম্ভাবনা × ১) − (ভুল হওয়ার সম্ভাবনা × কাটা নম্বর)। যেমন দুটি অপশন বাকি আর ০.৫ কাটা: ০.৫ − ০.৫ × ০.৫ = ০.২৫. শিক্ষা পরিষ্কার: অন্তত দুটি অপশন নিশ্চিতভাবে বাদ দিতে পারলে আন্দাজ সাধারণত লাভজনক; কিছুই না জানলে আর কাটা নম্বর বেশি হলে ছেড়ে দিন। কিন্তু “নিশ্চিতভাবে” কথাটিই আসল — ভুল কারণে বাদ দিলে হিসাব উল্টে যায়।

কৌশল ৪ · সময় ভাগ করা

ধরুন ১০০টি প্রশ্ন ৬০ মিনিটে — প্রতিটির জন্য গড়ে ৩৬ সেকেন্ড। একটি closure বা MRO-র প্রশ্নেই দুই মিনিট চলে যেতে পারে। তাই দুই দফায় চলুন। প্রথম দফায় সংজ্ঞা, keyword, কমপ্লেক্সিটি, “কোন module”-এর মতো সরাসরি প্রশ্ন দ্রুত শেষ করুন। কোড-ভারী প্রশ্নে দাগ দিয়ে এগিয়ে যান। দ্বিতীয় দফায় ফিরে এসে সেগুলো ধরুন। এক প্রশ্নে আটকে থেকে শেষের পাঁচটি সহজ প্রশ্ন না দেখা — MCQ পরীক্ষায় এটিই সবচেয়ে দামি ভুল।

এই পাতার প্রশ্নগুলো সম্পর্কে: প্রশ্নগুলো এ ধরনের পরীক্ষায় প্রায়ই আসে এমন ধারণা নিয়ে বানানো — কোনোটিই কোনো নির্দিষ্ট পরীক্ষার আসল প্রশ্ন নয়। প্রতিটি আউটপুট আর exception CPython 3.14-এ চালিয়ে যাচাই করা। প্রশ্নের শুরুতে কঠিনতার মাত্রা দেওয়া আছে: (সহজ), (মাঝারি), (কঠিন)।

C/C++ জানলে

C/C++-এর MCQ-তে বড় ফাঁদ “compile error” আর undefined behavior; Python-এ undefined behavior প্রায় নেই — তার জায়গায় আসে নির্দিষ্ট exception. কয়েকটি উত্তর উল্টে যায়: Python-এ -7 // 2 হলো −৪ (C-তে -7 / 2 হলো −৩), int-এ overflow হয় না, আর = কপি করে না, শুধু নাম বাঁধে। একই ধাঁচের প্রশ্নব্যাংক আছে C কোর্সে আর C++ কোর্সে; যিনি C শেখেননি, তিনি এই বাক্সটি বাদ দিতে পারেন।

২ · ভিত্তি — Python, interpreter আর নামের নিয়ম

এই অংশের প্রশ্ন সহজ মনে হয়, তাই সবাই তাড়াহুড়া করেন — আর ঠিক সেখানেই ভুল হয়। সবচেয়ে বেশি আসে: Python-এর ইতিহাস, কোড কীভাবে চলে (interpreter, bytecode), কোন শব্দ keyword, আর PEP 8-এর নামকরণ।

(সহজ) Python ভাষা মূলত কে তৈরি করেন?

  • Dennis Ritchie
  • Guido van Rossum
  • James Gosling
  • Bjarne Stroustrup

(সহজ) python app.py চালালে CPython কোডটি কীভাবে চালায়?

  • সরাসরি CPU-র machine code-এ রূপান্তর করে চালায়
  • আগে bytecode-এ রূপান্তর করে, তারপর virtual machine সেই bytecode চালায়
  • কোনো রূপান্তর ছাড়া লেখা লাইনগুলো একটি একটি করে পড়ে চালায়
  • কোডটি Java Virtual Machine-এ পাঠায়

(সহজ) কোনটি বৈধ ভ্যারিয়েবল নাম (identifier)?

  • 2total
  • my-var
  • _total2
  • class

(মাঝারি) Python 3-এ কোনটি keyword নয়?

  • print
  • pass
  • None
  • async

(মাঝারি) কোনটি একটি soft keyword — শুধু নির্দিষ্ট জায়গায় keyword, অন্য জায়গায় সাধারণ নাম হিসেবে ব্যবহার করা যায়?

  • lambda
  • yield
  • nonlocal
  • match

(সহজ) PEP 8 অনুযায়ী একটি ফাংশনের নাম কীভাবে লেখা উচিত?

  • CalculateTotal
  • calculateTotal
  • calculate_total
  • CALCULATE_TOTAL

(কঠিন) কী ছাপা হবে?

print(type(type))
  • <class 'type'>
  • <class 'object'>
  • TypeError
  • <class 'class'>

(মাঝারি) একটি ফাংশনের bytecode নির্দেশগুলো মানুষের পড়ার মতো করে দেখতে কোন standard module?

  • pdb
  • timeit
  • dis
  • ast

৩ · টাইপ, সংখ্যা, অপারেটর আর string

এখানে ছোট একটি লাইনে অনেক নিয়ম লুকিয়ে থাকে — / আর //-এর পার্থক্য, ঋণাত্মক সংখ্যার floor division, float-এর rounding error, truthiness, আর identity বনাম equality. C বা অন্য ভাষা থেকে আসা পাঠকের সবচেয়ে বেশি ভুল হয় এই অংশে।

(সহজ) কী ছাপা হবে?

print(7 / 2, 7 // 2)
  • 3 3
  • 3.5 3
  • 3.5 3.5
  • 3.0 3

(মাঝারি) কী ছাপা হবে?

print(-7 // 2, -7 % 2)
  • -3 -1
  • -3 1
  • -4 1
  • -4 -1

(সহজ) print(2 ** 3 ** 2) কী ছাপবে?

  • 64
  • 36
  • 512
  • SyntaxError

(মাঝারি) print(0.1 + 0.2 == 0.3) কী ছাপবে?

  • True
  • False
  • TypeError
  • 0.3

(সহজ) কোনটির truth value False (falsy)?

  • "0"
  • [0]
  • " "
  • 0.0

(সহজ) কী ছাপা হবে?

a = [1, 2]
b = [1, 2]
print(a == b, a is b)
  • True True
  • True False
  • False False
  • False True

(মাঝারি) একটি ভ্যারিয়েবল None কি না যাচাই করার PEP 8-সম্মত রীতি কোনটি?

  • if x == None:
  • if x is None:
  • if x === None:
  • if None in x:

(মাঝারি) কোন লাইনটি SyntaxError দেবে?

  • print(n := 10)
  • (n := 10)
  • n := 10
  • if (n := 10) > 5: pass

(মাঝারি) Python 3.8 বা পরের সংস্করণে কী ছাপা হবে?

x = 5
print(f"{x=}")
  • 5
  • x=5
  • {x=}
  • SyntaxError

(মাঝারি) len("বাংলা")-এর মান কত?

  • 3
  • 5
  • 15
  • 10

(মাঝারি) কী হবে?

s = "cat"
s[0] = "b"
  • s হয়ে যায় "bat"
  • AttributeError
  • TypeError
  • IndexError

(কঠিন) কী ছাপা হবে?

print(round(2.5), round(3.5))
  • 3 4
  • 2 4
  • 2 3
  • 3 3

এবার দুটি অংশের সংখ্যা ও টাইপের দাবিগুলো নিজের চোখে দেখুন। কোডটি চালান — এতে কোনো ফাঁদ নেই, শুধু তথ্য:

print(bool([]), bool("0"), bool(0.0))
print(type(1 / 2).__name__, 7 // 2, -7 // 2, -7 % 2)
print(0.1 + 0.2, 0.1 + 0.2 == 0.3)
print(round(2.5), round(3.5), 2 ** 3 ** 2)
print(len("বাংলা"), len("বাংলা".encode("utf-8")))
print(sum([0.1] * 10) == 1.0)

CPython 3.14-এ ফল: False True False, তারপর float 3 -4 1, 0.30000000000000004 False, 2 4 512, 5 15 আর শেষে True. শেষ লাইনটি একটি মজার ফাঁদ: দশটি 0.1 একটি একটি করে + দিয়ে যোগ করলে ফল 0.9999999999999999 — 1.0-এর সমান নয়। কিন্তু Python 3.12 থেকে sum() float যোগে একটি ক্ষতিপূরণ-পদ্ধতি (compensated summation) ব্যবহার করে, তাই এখানে ঠিক 1.0 আসে। পুরোনো সংস্করণে একই লাইন False দিত — সংস্করণ না জানিয়ে এমন প্রশ্ন করা অন্যায্য। bool("0") True, কারণ string-টি খালি নয়।

৪ · সিদ্ধান্ত, লুপ, ফাংশন আর scope

এই অংশের প্রশ্নে কোড ছোট, কিন্তু নিয়ম সূক্ষ্ম। range-এর শেষ মান বাদ যায়, লুপের else কখন চলে, match-এ নাম লিখলে তুলনা নয় বরং capture হয়, আর ফাংশনের mutable default একবারই তৈরি হয়। Scope-এর প্রশ্নে সবচেয়ে প্রচলিত ভুল: ভাবা যে একটি নাম ফাংশনের মাঝপথে local হয়।

(সহজ) list(range(2, 10, 3))-এর মান কী?

  • [2, 5, 8]
  • [2, 5, 8, 11]
  • [3, 6, 9]
  • [2, 4, 6, 8]

(মাঝারি) for লুপের সাথে লেখা else block কখন চলে?

  • লুপ break দিয়ে থামলে
  • লুপ break ছাড়া পুরোপুরি শেষ হলে
  • কখনো না — for-এর সাথে else লিখলে SyntaxError
  • শুধু তখন, যখন লুপ একবারও ঘোরেনি

(মাঝারি) list(zip([1, 2, 3], "ab"))-এর মান কী?

  • ValueError
  • [(1, 'a'), (2, 'b'), (3, None)]
  • [(1, 'a'), (2, 'b')]
  • [(1, 2, 3), ('a', 'b')]

(কঠিন) কী হবে?

RED = "red"
match "blue":
    case RED:
        print("red!")
    case _:
        print("other")
  • red!
  • other
  • SyntaxError
  • NameError

(মাঝারি) def f(*args, **kwargs) ফাংশনকে f(1, 2, a=3) দিয়ে ডাকলে args আর kwargs-এর টাইপ কী?

  • list আর dict
  • tuple আর dict
  • tuple আর list
  • list আর tuple

(কঠিন) কী ছাপা হবে?

def add(x, items=[]):
    items.append(x)
    return items

add(1)
print(add(2))
  • [2]
  • [1, 2]
  • [1]
  • TypeError

(মাঝারি) কী হবে?

x = 1
def f():
    print(x)
    x = 2
f()
  • 1
  • 2
  • UnboundLocalError
  • NameError: name 'x' is not defined

(কঠিন) কী ছাপা হবে?

fs = [lambda: i for i in range(3)]
print([f() for f in fs])
  • [0, 1, 2]
  • [0, 0, 0]
  • NameError
  • [2, 2, 2]

(মাঝারি) nonlocal keyword কেন লেখা হয়?

  • ভেতরের ফাংশন থেকে বাইরের (enclosing) ফাংশনের ভ্যারিয়েবলে নতুন মান বসাতে
  • module-স্তরের (global) ভ্যারিয়েবল বদলাতে
  • ফাংশনের ভেতরে নতুন local ভ্যারিয়েবল ঘোষণা করতে
  • অন্য module থেকে নাম আনতে

(মাঝারি) def f(a, *, b): ... থাকলে f(1, 2) ডাকলে কী হয়?

  • a=1, b=2 বসে
  • TypeError
  • SyntaxError (def লাইনেই)
  • a=1, b=None

৫ · ডেটা স্ট্রাকচার, mutability আর copy

চাকরি আর ইন্টারভিউর পরীক্ষায় এই অংশ থেকেই সবচেয়ে বেশি প্রশ্ন আসে। দুটি বিষয়ের ওপর জোর দিন। এক, কোন কাজে কোন ডেটা স্ট্রাকচার কত দ্রুত — list-এ খোঁজা O(n), set আর dict-এ গড়ে O(1), কারণ এরা hash table. দুই, কোন object immutable, কোনটি hashable, আর =, shallow copy আর deep copy-এর পার্থক্য।

(সহজ) type((5))-এর ফল কী?

  • <class 'tuple'>
  • <class 'int'>
  • <class 'list'>
  • SyntaxError

(সহজ) কী ছাপা হবে?

a = [1, 2, 3]
b = a
b.append(4)
print(a)
  • [1, 2, 3]
  • None
  • [1, 2, 3, 4]
  • AttributeError

(মাঝারি) দশ লাখ ব্যবহারকারীর নামের মধ্যে কোনো নাম আছে কি না বারবার যাচাই করতে হবে। নামগুলো কোন ডেটা স্ট্রাকচারে রাখলে name in ... সবচেয়ে দ্রুত?

  • list
  • tuple
  • set
  • সব নাম জোড়া লাগানো একটি str

(মাঝারি) n উপাদানের list-এ lst.insert(0, x)-এর time complexity কত?

  • O(n)
  • O(1)
  • O(log n)
  • O(n log n)

(মাঝারি) d = {[1, 2]: "a"} চালালে কী হয়?

  • dict তৈরি হয়, key হলো [1, 2]
  • TypeError
  • KeyError
  • ValueError

(সহজ) d = {"a": 1} হলে কোনটি exception দেবে?

  • d.get("b")
  • "b" in d
  • d["b"]
  • d.setdefault("b", 0)

(কঠিন) কী ছাপা হবে?

import copy
a = [[1], [2]]
b = copy.copy(a)
b[0].append(9)
print(a)
  • [[1, 9], [2]]
  • [[1], [2]]
  • [[1], [2], [9]]
  • TypeError

(মাঝারি) কী ছাপা হবে?

g = [[0] * 2] * 2
g[0][0] = 1
print(g)
  • [[1, 0], [0, 0]]
  • [[1, 1], [0, 0]]
  • TypeError
  • [[1, 0], [1, 0]]

(মাঝারি) কী হবে?

t = ([1], 2)
t[0].append(3)
print(t)
  • TypeError
  • ([1, 3], 2)
  • ([1], 2)
  • AttributeError

(মাঝারি) কী ছাপা হবে?

x = [3, 1, 2].sort()
print(x)
  • [1, 2, 3]
  • None
  • [3, 1, 2]
  • AttributeError

(মাঝারি) a, *b = [1, 2, 3] লিখলে b-এর মান কী?

  • (2, 3)
  • [2, 3]
  • 2
  • ValueError

(মাঝারি) [x * x for x in range(5) if x % 2]-এর মান কী?

  • [0, 4, 16]
  • [1, 9]
  • [1, 9, 25]
  • [0, 1, 4, 9, 16]

এবার এই দুই অংশের সবচেয়ে বিখ্যাত পাঁচটি ফাঁদ একসাথে চালিয়ে দেখুন:

import copy

def add(x, items=[]):
    items.append(x)
    return items

add(1)
print(add(2))

grid = [[0] * 2] * 2
grid[0][0] = 1
print(grid)

a = [[1], [2]]
s, d = copy.copy(a), copy.deepcopy(a)
a[0].append(9)
print(s, d)

fs = [lambda: i for i in range(3)]
print([f() for f in fs])

g = (x * x for x in range(3))
print(sum(g), sum(g))

ফল পাঁচ লাইনে: [1, 2] (default list একবারই তৈরি), [[1, 0], [1, 0]] (দুটি সারি একই object), [[1, 9], [2]] [[1], [2]] (shallow copy ভেতরের list ভাগ করে, deep copy করে না), [2, 2, 2] (closure-এর late binding) আর 5 0 — একটি generator expression একবার শেষ হলে খালি হয়ে যায়, তাই দ্বিতীয় sum কিছুই পায় না। পাঁচটির মূল কারণ একটিই: Python-এ নাম object-কে দেখায়, কপি করে না।

৬ · বড় প্রোগ্রাম — module, exception, ফাইল, ডেটা আর regex

এই অংশে প্রশ্ন আসে দুই ধরনের: ধারণা (__name__, virtual environment, exception hierarchy) আর “কোন জিনিস কোন কাজে” — কোন file mode, কোন module, কোন regex ফাংশন। try-এর else আর finally-এর ক্রম ভালোভাবে জানুন; এটি প্রায়ই আসে।

(সহজ) টার্মিনালে python app.py চালালে app.py-এর ভেতরে __name__-এর মান কী?

  • "app"
  • "__main__"
  • "app.py"
  • None

(মাঝারি) একটি প্রোগ্রামে দুটি আলাদা ফাইল একই module import করলে সেই module-এর top-level কোড কতবার চলে?

  • প্রতিটি import-এ একবার — মোট দুবার
  • একবার — পরের import sys.modules থেকে একই object নেয়
  • একবারও না — import শুধু নাম আনে
  • ImportError হয়

(সহজ) python -m venv .venv কী করে?

  • কম্পিউটারে Python-এর নতুন সংস্করণ install করে
  • প্রজেক্টের জন্য একটি pyproject.toml ফাইল বানায়
  • এই প্রজেক্টের জন্য একটি আলাদা Python পরিবেশ বানায়, যেখানে install করা package অন্য প্রজেক্টে মেশে না
  • প্রজেক্টের সব package নিজে থেকে install করে

(মাঝারি) except Exception: কোনটিকে ধরবে না?

  • ValueError
  • KeyError
  • KeyboardInterrupt
  • ZeroDivisionError

(মাঝারি) কী ছাপা হবে?

try:
    x = 1
except ValueError:
    print("E")
else:
    print("L")
finally:
    print("F")
  • E, তারপর F
  • L, তারপর F
  • শুধু F
  • শুধু L

(কঠিন) Python 3.14-এ এই কোড কী করে?

def f():
    try:
        return 1
    finally:
        return 2
print(f())
  • 1 ছাপে
  • 2 ছাপে — সাথে একটি SyntaxWarning দেখায়
  • SyntaxError
  • 1, তারপর 2 ছাপে

(সহজ) কোন mode-এ open() করলে ফাইলের আগের সব লেখা মুছে যায়?

  • "r"
  • "a"
  • "w"
  • "x"

(মাঝারি) বাংলা লেখা ফাইলে লিখতে হবে, আর প্রোগ্রামটি Windows, Linux সব জায়গায় একই রকম চলতে হবে। কোনটি সবচেয়ে নিরাপদ?

  • open(p, "w")
  • open(p, "wb")
  • open(p, "w", encoding="utf-8")
  • open(p, "a")

(মাঝারি) json.dumps({"a": True, "b": None})-এর ফল কোন string?

  • {'a': True, 'b': None}
  • {"a": true, "b": null}
  • {"a": True, "b": None}
  • TypeError

(সহজ) একটি লেখায় প্রতিটি শব্দ কতবার আছে গুনে সবচেয়ে বেশি আসা ৩টি শব্দ বের করতে কোন standard library টুল সবচেয়ে উপযুক্ত?

  • random.choice
  • datetime
  • collections.deque
  • collections.Counter

(মাঝারি) কী ছাপা হবে?

import re
print(re.match(r"\d+", "ab12"))
  • None
  • <re.Match object; span=(2, 4), match='12'>
  • 12
  • re.error

(কঠিন) re.findall(r"<.+>", "<a><b>")-এর ফল কী?

  • ['<a>', '<b>']
  • ['<a><b>']
  • ['a', 'b']
  • []

৭ · OOP — class, dunder method, inheritance, generator আর decorator

OOP-এর প্রশ্ন দুই ধরনের। এক ধরনের প্রশ্ন পরিভাষা চায় — __init__, dunder method, ABC, Protocol. আরেক ধরনের প্রশ্ন ছোট কোড দিয়ে যাচাই করে: class attribute সব object ভাগ করে কি না, MRO-তে কোন class আগে আসে, আর generator দ্বিতীয়বার চালালে কী হয়। দ্বিতীয় ধরনটিতেই বেশি ভুল হয়।

(সহজ) method-এর প্রথম parameter self সম্পর্কে কোনটি সত্য?

  • এটি একটি reserved keyword; অন্য নাম দিলে SyntaxError
  • এটি keyword নয়, শুধু প্রচলিত নাম — method ডাকলে যে object-এর ওপর ডাকা হলো, সেটি এখানে আসে
  • এটি সব সময় class-টিকে দেখায়, object-কে নয়
  • এটি শুধু __init__-এ লাগে, অন্য method-এ লাগে না

(মাঝারি) obj = C() লিখলে নতুন object আসলে তৈরি (মেমরিতে বানানো) করে কোন method?

  • __init__
  • __call__
  • __new__
  • __create__

(মাঝারি) কী ছাপা হবে?

class C:
    items = []

a, b = C(), C()
a.items.append(1)
print(b.items)
  • []
  • [1]
  • AttributeError
  • None

(কঠিন) কী ছাপা হবে?

class C:
    def __str__(self): return "S"
    def __repr__(self): return "R"
print(C(), [C()])
  • S [S]
  • R [R]
  • S [R]
  • R [S]

(কঠিন) কী হবে?

class P:
    def __eq__(self, o):
        return True

s = {P()}
  • একটি উপাদানের set তৈরি হয়
  • TypeError
  • ValueError
  • একটি খালি set তৈরি হয়

(মাঝারি) কী ছাপা হবে?

class A: name = "A"
class B(A): name = "B"
class C(A): name = "C"
class D(B, C): pass
print(D.name, D.__mro__[2].__name__)
  • B C
  • B A
  • C B
  • TypeError

(মাঝারি) কী হবে?

class T:
    @property
    def x(self):
        return 1

t = T()
t.x = 5
  • t.x-এর মান 5 হয়ে যায়
  • AttributeError
  • TypeError
  • SyntaxError

(কঠিন) কী হবে?

from dataclasses import dataclass

@dataclass
class Cart:
    items: list = []
  • সব Cart একই list ভাগ করে
  • প্রতিটি Cart নিজের আলাদা list পায়
  • ValueError — class তৈরির সময়েই
  • object বানানোর সময় TypeError

(মাঝারি) abc.ABC থেকে তৈরি একটি class-এ একটি @abstractmethod আছে, যার কোনো subclass-এ implementation নেই। সেই class-এর object বানাতে চাইলে কী হয়?

  • object তৈরি হয়; method ডাকলে তবেই error
  • SyntaxError
  • NotImplementedError
  • TypeError

(মাঝারি) typing.Protocol দিয়ে বানানো একটি interface-এর সাথে কোনো class-কে মেলাতে কী লাগে?

  • class-এ দরকারি method আর attribute একই নামে থাকলেই যথেষ্ট — inherit করতে হয় না
  • class-টিকে অবশ্যই Protocol class থেকে inherit করতে হবে
  • class-টিকে register() দিয়ে নিবন্ধন করতে হবে
  • runtime-এ Python নিজে থেকে প্রতিটি ডাক যাচাই করে

(মাঝারি) কী ছাপা হবে?

g = (x * x for x in range(3))
print(sum(g), sum(g))
  • 5 5
  • 5 0
  • 0 5
  • StopIteration

(মাঝারি) ফাংশনের ওপর @timer লেখা কোন কোডের সমান?

@timer
def work(): ...
  • work = timer(work())
  • timer = work(timer)
  • work = timer(work)
  • প্রতিবার work() ডাকার আগে timer() ডাকা

এই অংশের তিনটি দাবি — MRO-র ক্রম, ভাগ করা class attribute আর __eq__-এর পরে hash হারানো — নিজে চালিয়ে দেখুন:

class A: name = "A"
class B(A): name = "B"
class C(A): name = "C"
class D(B, C): pass

print([k.__name__ for k in D.__mro__])

class Box:
    items = []

p, q = Box(), Box()
p.items.append(1)
print(q.items)

class P:
    def __eq__(self, other):
        return True

print(P.__hash__)
try:
    {P()}
except TypeError as e:
    print(type(e).__name__)

ফল চার লাইনে: ['D', 'B', 'C', 'A', 'object'] — C3 linearization-এ সাধারণ base A দুই শাখার পরে আসে; [1] — class attribute সব instance-এর জন্য একটিই list; None — __eq__ লেখার সাথে সাথে Python __hash__-কে None করে দিয়েছে; আর TypeError — তাই set-এ রাখা যায় না। try/except দিয়ে ধরেছি বলে প্রোগ্রাম থামেনি, আর stderr-এ কিছু যায়নি।

৮ · আধুনিক Python — context manager, type hint, টেস্ট, concurrency আর পারফরম্যান্স

এই অংশের প্রশ্নে সংস্করণ গুরুত্বপূর্ণ। type hint চলার সময় কিছুই জোর করে না, logging-এর ডিফল্ট স্তর WARNING, আর GIL নিয়ে Python 3.13–3.14-এ বড় পরিবর্তন এসেছে। asyncio-র প্রশ্নে মূল ধারণা একটিই: thread নয়, একটি thread-এর ভেতরে পালা করে কাজ।

(সহজ) with statement-এ ব্যবহার করতে একটি object-এর কোন দুটি method লাগে?

  • __init__ আর __del__
  • __enter__ আর __exit__
  • __open__ আর __close__
  • __iter__ আর __next__

(কঠিন) কী ছাপা হবে?

class M:
    def __enter__(self): return self
    def __exit__(self, *a): return True

with M():
    1 / 0
print("ok")
  • ZeroDivisionError
  • ok
  • কিছুই ছাপে না
  • TypeError

(সহজ) কী ছাপা হবে?

def f(x: int) -> int:
    return x
print(f("a"))
  • TypeError
  • a
  • None
  • SyntaxError

(মাঝারি) mypy কী করে?

  • কোড না চালিয়েই type hint মিলিয়ে টাইপের ভুল খোঁজে
  • কোডকে compile করে দ্রুত চালায়
  • চলার সময় প্রতিটি ফাংশনের argument-এর টাইপ জোর করে মানায়
  • কোড PEP 8 অনুযায়ী format করে

(মাঝারি) একটি pure ফাংশন একই argument দিয়ে বারবার ডাকা হয়, আর প্রতিবার হিসাব ধীর। আগের ফল মনে রাখতে (memoization) কোনটি?

  • itertools.cycle
  • operator.itemgetter
  • functools.lru_cache
  • contextlib.suppress

(সহজ) ডিফল্ট সেটিংসে pytest কোন ফাংশনকে নিজে থেকে টেস্ট হিসেবে খুঁজে চালায় (ফাইলের নাম test_cart.py)?

  • def check_total():
  • def test_total():
  • def total_test():
  • def TotalTest():

(মাঝারি) কোনো logging কনফিগারেশন ছাড়া একটি স্ক্রিপ্টে শুধু import logging আর logging.info("hi") লিখে চালালে কী দেখা যায়?

  • hi
  • INFO:root:hi
  • কিছুই দেখা যায় না
  • NameError

(মাঝারি) ডিফল্ট (GIL-সহ) CPython-এ একটি বিশুদ্ধ-Python, CPU-নির্ভর হিসাব ৮টি core-এ ভাগ করে দ্রুত করতে চান। কোনটি কাজ করবে?

  • threading দিয়ে ৮টি thread
  • asyncio দিয়ে ৮টি task
  • multiprocessing বা ProcessPoolExecutor
  • প্রতিটি ধাপে time.sleep(0) দেওয়া

(কঠিন) free-threaded (GIL ছাড়া) CPython সম্পর্কে কোনটি সত্য?

  • Python 3.14 থেকে সব build-এ GIL পুরোপুরি সরানো হয়েছে
  • Python 3.13-এ পরীক্ষামূলক হিসেবে এসেছে; 3.14-এ আনুষ্ঠানিকভাবে সমর্থিত, কিন্তু ডিফল্ট build নয়
  • এটি শুধু asyncio-র কাজ দ্রুত করে
  • GIL শুধু I/O-কে ধীর করে, তাই free-threading শুধু ফাইল পড়ায় কাজে লাগে

(মাঝারি) async def fetch(): ... লেখা আছে। কোনো await ছাড়া সাধারণ কোড থেকে fetch() ডাকলে কী হয়?

  • ফাংশনের body চলে আর ফল ফেরত আসে
  • একটি coroutine object পাওয়া যায়, কিন্তু body চলে না
  • SyntaxError
  • ফাংশনটি একটি নতুন thread-এ চলতে শুরু করে

(মাঝারি) একটি বড় প্রোগ্রাম ধীর। কোন ফাংশনে সবচেয়ে বেশি সময় যাচ্ছে বের করতে কোন standard টুল?

  • timeit
  • cProfile
  • pdb
  • tracemalloc

৯ · বাস্তব প্রয়োগ — Pythonic কোড, অ্যালগরিদম, ডেটা, ওয়েব আর নিরাপত্তা

শেষ অংশের প্রশ্ন বেশিরভাগ “কোন টুল কোন কাজে” আর “কোনটি নিরাপদ” ধরনের। ইন্টারভিউতে এখান থেকে আসে Pythonic লেখা, সাজানোর কমপ্লেক্সিটি, NumPy-র vectorization, HTTP status code আর SQL injection ঠেকানো।

(সহজ) list-এর উপাদান আর তার ইনডেক্স দুটিই লুপে দরকার। সবচেয়ে Pythonic লেখা কোনটি?

  • for i in range(len(items)):
  • for i, x in enumerate(items):
  • while i < len(items):
  • for i, x in items:

(মাঝারি) ruff কী?

  • একটি static type checker
  • একটি package manager
  • Rust-এ লেখা একটি দ্রুত linter ও formatter
  • একটি test runner

(মাঝারি) Python-এর sorted() সম্পর্কে কোনটি সত্য?

  • stable — সমান key-এর উপাদানের আগের ক্রম বজায় থাকে; খারাপতম অবস্থায় O(n log n)
  • quicksort ব্যবহার করে, খারাপতম অবস্থায় O(n²)
  • নতুন list দেয় না, মূল list জায়গায় সাজায়
  • শুধু সংখ্যা সাজাতে পারে

(মাঝারি) BFS-এর queue হিসেবে list-এর pop(0)-এর বদলে কোনটি ব্যবহার করা উচিত, আর কেন?

  • tuple, কারণ এটি দ্রুত
  • set, কারণ খোঁজা O(1)
  • heapq, কারণ এটি সব সময় সাজানো থাকে
  • collections.deque, কারণ popleft() O(1)

(মাঝারি) import numpy as np করা আছে। [1, 2, 3] * 2 আর np.array([1, 2, 3]) * 2-এর ফল কী?

  • দুটিতেই [2, 4, 6]
  • list → [1, 2, 3, 1, 2, 3], array → [2 4 6]
  • দুটিতেই উপাদানের পুনরাবৃত্তি
  • list-এ TypeError, array → [2 4 6]

(মাঝারি) pandas-এ df["price"] লিখে একটি কলাম বাছলে কোন টাইপের object পাওয়া যায়?

  • Series
  • DataFrame
  • list
  • dict

(সহজ) একটি API-তে POST করে নতুন resource সফলভাবে তৈরি হলে প্রচলিত HTTP status code কোনটি?

  • 200
  • 201
  • 204
  • 404

(মাঝারি) r = requests.get(url, timeout=5)-এ সার্ভার 404 ফেরত দিলে কী হয়?

  • সাথে সাথে requests.HTTPError ওঠে
  • r-এর মান None হয়
  • কোনো exception নয়; r.status_code হয় 404, আর r.raise_for_status() ডাকলে তবে HTTPError
  • requests.Timeout ওঠে

(মাঝারি) FastAPI-তে @app.get("/items/{item_id}") আর ফাংশনে item_id: int আছে। কেউ /items/abc-এ অনুরোধ পাঠালে ডিফল্টে কী হয়?

  • ফাংশন চলে, আর item_id-এ "abc" বসে
  • 500 Internal Server Error
  • 404 Not Found
  • 422 — যাচাই ব্যর্থ, কোন field ভুল তার JSON বিবরণসহ

(মাঝারি) argparse-এ --verbose এমন একটি flag বানাতে চান, যা লিখলে True, না লিখলে False. কোন argument?

  • type=bool
  • nargs=0
  • action="store_true"
  • required=True

(কঠিন) ব্যবহারকারীর দেওয়া ফাইলের নাম name দিয়ে একটি কমান্ড চালাতে হবে। কোনটি সবচেয়ে নিরাপদ?

  • subprocess.run(["wc", "-l", name])
  • subprocess.run(f"wc -l {name}", shell=True)
  • os.system("wc -l " + name)
  • eval(name) দিয়ে আগে যাচাই করে তারপর চালানো

(মাঝারি) sqlite3-এ ব্যবহারকারীর দেওয়া name দিয়ে খুঁজতে কোনটি SQL injection থেকে নিরাপদ?

  • cur.execute(f"... WHERE name = '{name}'")
  • cur.execute("... WHERE name = '%s'" % name)
  • cur.execute("... WHERE name = '" + name + "'")
  • cur.execute("... WHERE name = ?", (name,))

(মাঝারি) কোনটি নিরাপত্তার দিক থেকে বিপজ্জনক অভ্যাস?

  • পাসওয়ার্ড রিসেট টোকেন secrets.token_urlsafe() দিয়ে বানানো
  • API key os.environ থেকে পড়া
  • ইন্টারনেট থেকে পাওয়া ডেটা pickle.loads() দিয়ে খোলা
  • ব্যবহারকারীর ইনপুটের দৈর্ঘ্য ও ধরন আগে যাচাই করা

১০ · AI দিয়ে আরও MCQ বানান — কিন্তু উত্তর চালিয়ে যাচাই করুন

এই পাতার প্রশ্ন শেষ হলে আরও অনুশীলন চাইবেন। AI চ্যাটবট মুহূর্তে শত শত MCQ বানিয়ে দিতে পারে। কিন্তু একটি সতর্কবার্তা মনে রাখুন: Python-এর চতুর MCQ-তে AI প্রায়ই ভুল অপশনকে “সঠিক” বলে। কারণ ইন্টারনেটের পুরোনো প্রশ্নব্যাংকে Python 2-এর যুগের উত্তর বছরের পর বছর ছড়িয়েছে — যেমন 7 / 2-এর উত্তর 3, বা “dict-এর ক্রম নিশ্চিত নয়” (Python 3.7 থেকে ভাষার নিয়মেই insertion order নিশ্চিত)। তার ওপর Python প্রতি বছর একটি নতুন সংস্করণ আনে; AI অনেক সময় 3.8 আর 3.14-এর নিয়ম মিশিয়ে ফেলে।

AI-এর প্রচলিত ভুলউদাহরণকীভাবে ধরবেন
Python 2-এর নিয়ম7 / 2 → “3”, print "x" বৈধপ্রশ্নটি Python 3.14-এ চালান
স্কুলের গণিত ধরে নেওয়াround(2.5) → “3”, -7 // 2 → “-3”REPL-এ এক লাইন লিখে দেখুন
সংস্করণ উল্লেখ না করাsum([0.1] * 10) == 1.0 → “False” (3.12 থেকে True)প্রশ্নে সংস্করণ লেখা আছে কি না দেখুন
exception-এর ভুল নামassignment-এর আগে local পড়া → “NameError”traceback-এর শেষ লাইনটিই উত্তর
implementation-এর খুঁটিনাটিকে নিয়ম বলা“a is b সব সমান int-এ True”CPython-এর ছোট int cache (−৫ থেকে ২৫৬) ভাষার নিয়ম নয় — এমন প্রশ্ন বাদ দিন
দুটি সঠিক অপশন“কোনটি falsy?” — 0 আর [] দুটিই অপশনেপ্রতিটি অপশন আলাদা করে চালান

নিচের প্রম্পটটি AI-কে প্রশ্ন বানাতে বলে, কিন্তু সাথে এমন তথ্যও চায়, যা দিয়ে আপনি উত্তর যাচাই করতে পারবেন। প্রতিটি কোড-প্রশ্নের জন্য চালানো যায় এমন একটি স্ক্রিপ্ট চাওয়াই এখানে আসল কৌশল:

আমি Python-এর MCQ পরীক্ষার প্রস্তুতি নিচ্ছি। ধরে নাও: CPython 3.14, শুধু standard library. বিষয়: [যেমন: mutability, copy আর scope] কঠিনতা: [সহজ / মাঝারি / কঠিন] ১০টি MCQ বানাও। প্রতিটির জন্য: ১. প্রশ্ন আর ঠিক ৪টি অপশন; শুধু একটি সঠিক। ২. সঠিক উত্তর, আর প্রতিটি ভুল অপশন কেন ভুল — এক লাইনে। ৩. প্রশ্নে কোড থাকলে একটি সম্পূর্ণ স্ক্রিপ্ট দাও, যা চালালে উত্তরটি দেখা যায় (exception হলে traceback-এর শেষ লাইন কী হবে, সেটাও লেখো)। ৪. উত্তর যদি exception হয়, তার পুরো নাম লেখো (যেমন UnboundLocalError) — শুধু "Error" লিখবে না। ৫. কোন সংস্করণ থেকে নিয়মটি চালু, তা লেখো (যেমন "Python 3.10 থেকে")। ৬. id(), set-এর ক্রম, ছোট int cache-এর মতো implementation-নির্ভর জিনিস নিয়ে প্রশ্ন করবে না। ৭. কোনো প্রশ্নকে কোনো নির্দিষ্ট পরীক্ষার আসল প্রশ্ন বলে দাবি করবে না।

যাচাইয়ের চেকলিস্ট

কেন এই উত্তর? — যে প্রশ্নগুলোতে সবচেয়ে বেশি ভুল হয়

এই ছয়টি বিষয় এত বেশি ভুল বোঝা হয় যে আলাদা ব্যাখ্যা দরকার। প্রশ্নটি নিয়ে আগে নিজে ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১list আর tuple-এর আসল পার্থক্য কী? “tuple শুধু একটি পরিবর্তন-অযোগ্য list” — এটুকু বললেই কি উত্তর সম্পূর্ণ?

না, এটুকু অর্ধেক উত্তর। কারিগরি পার্থক্য সত্যিই একটি: list mutable, tuple immutable — tuple তৈরির পর তার ঘরে অন্য object বসানো, উপাদান যোগ বা বাদ দেওয়া যায় না। কিন্তু এই একটি পার্থক্য থেকে তিনটি বড় পরিণতি আসে, আর ভালো উত্তরে সেগুলোই থাকে।

  • hashable হওয়া: যে tuple-এর সব উপাদান hashable, সেটি নিজেও hashable — তাই dict-এর key বা set-এর উপাদান হতে পারে। {(23.8, 90.4): "ঢাকা"} বৈধ, কিন্তু list দিয়ে একই কাজ করলে TypeError.
  • নিরাপদে ভাগ করা: একটি tuple কোনো ফাংশনে পাঠালে নিশ্চিত থাকেন যে ফাংশনটি সেটি বদলাবে না। list পাঠালে সেই নিশ্চয়তা নেই — alias-এর মাধ্যমে বদল ফিরে আসতে পারে।
  • সামান্য কম মেমরি ও দ্রুত তৈরি: tuple-এর আকার নির্দিষ্ট, তাই বাড়তি জায়গা (over-allocation) রাখতে হয় না। constant দিয়ে লেখা tuple literal compiler একবারেই একটি constant হিসেবে bytecode-এ রেখে দেয় (constant folding). তবে এই পার্থক্য ছোট; শুধু গতির জন্য tuple বাছাই করা ঠিক যুক্তি নয়।

এর বাইরে একটি রীতির পার্থক্যও আছে, যা Guido van Rossum নিজে বিভিন্ন সময় বলেছেন: list সাধারণত একই ধরনের জিনিসের ক্রম (সব ছাত্রের নাম), tuple সাধারণত বিভিন্ন ধরনের জিনিসের একটি রেকর্ড (নাম, রোল, GPA) — যেখানে অবস্থানেরই অর্থ আছে। এজন্যই ফাংশন একাধিক মান ফেরত দিলে tuple দেয়, আর namedtuple সেই অবস্থানগুলোকে নাম দেয়।

MCQ-তে সবচেয়ে বড় ফাঁদ: “tuple immutable, তাই তার ভেতরের কিছুই বদলায় না।” ভুল। t = ([1], 2)-এ t[0].append(3) চলে, কারণ immutability অগভীর (shallow) — tuple শুধু নিশ্চিত করে যে প্রথম ঘর সব সময় সেই একই list object দেখাবে; list-টি নিজে বদলাতে পারে। এমন tuple আবার hashable নয়, কারণ ভেতরের list unhashable. দ্বিতীয় ফাঁদ: (5) tuple নয়, int — tuple বানায় কমা, বন্ধনী নয়; এক উপাদানের tuple লিখতে হয় (5,).

মূল উপলব্ধি: পার্থক্যের মূলে mutability, কিন্তু উত্তরের মূল্য তার পরিণতিতে — tuple hashable হতে পারে, নিরাপদে ভাগ করা যায়, আর একটি রেকর্ড বোঝায়। আর tuple-এর immutability অগভীর: ভেতরের mutable object বদলাতে পারে।

প্র ০২is আর ==-এর পার্থক্য কী? a = 256; b = 256-এ a is b True হলে কি সব সমান সংখ্যায় is ব্যবহার করা যায়?

== জিজ্ঞেস করে “মান কি সমান?” — ভেতরে __eq__ method ডাকে, আর প্রতিটি টাইপ নিজে ঠিক করে সমানতা কী। is জিজ্ঞেস করে “এরা কি একই object?” — দুটি নাম মেমরির একই object দেখায় কি না। is override করা যায় না, তাই এটি দ্রুত আর সব সময় নিখুঁত।

উদাহরণ: [1, 2] == [1, 2] True, কিন্তু [1, 2] is [1, 2] False — দুটি আলাদা list তৈরি হয়েছে। আবার b = a লিখলে a is b True, কারণ নতুন কোনো object তৈরি হয়নি, শুধু নতুন নাম।

তাহলে 256 is 256 কেন True? এটি CPython-এর একটি অপ্টিমাইজেশন, ভাষার নিয়ম নয়। CPython শুরুতেই −৫ থেকে ২৫৬ পর্যন্ত int object বানিয়ে রাখে (small int cache), আর এই সীমার প্রতিটি মান সব জায়গায় সেই একই object দেয়। একইভাবে কিছু ছোট string, যেমন identifier-এর মতো দেখতে লেখা, স্বয়ংক্রিয়ভাবে interning হয়। এই সীমার বাইরে ফল নির্ভর করে কোড কোথায় লেখা — একই ফাইলে compile হওয়া দুটি 1000 literal একই constant হতে পারে, আবার REPL-এ আলাদা লাইনে লিখলে আলাদা object হতে পারে। PyPy-র মতো অন্য implementation-এর নিয়ম আবার আলাদা।

এই অনিশ্চয়তার কারণেই Python 3.8 থেকে literal-এর সাথে is লিখলে (x is 5) compiler একটি SyntaxWarning দেয়: “"is" with 'int' literal. Did you mean "=="?” অর্থাৎ ভাষা নিজেই বলছে, মান তুলনায় is নয়।

তাহলে is কখন? যখন আপনি সত্যিই identity জানতে চান, বিশেষ করে singleton-এর ক্ষেত্রে: x is None, x is True (খুব কম দরকার), বা নিজের বানানো sentinel object (_MISSING = object())। None-এর জন্য PEP 8 স্পষ্টভাবে is চায়, কারণ কোনো class __eq__ বদলে obj == None-কে True বানাতে পারে — NumPy array-তে arr == None তো একটি bool-এর array দেয়।

মূল উপলব্ধি: == মান, is পরিচয়। ছোট int বা string-এ is-এর True হওয়া CPython-এর cache-এর ফল, নিশ্চয়তা নয় — মান তুলনায় সব সময় ==, আর None বা sentinel-এ is.

প্র ০৩mutable default argument কেন আগের ডাকের মান মনে রাখে? এটি কি Python-এর একটি বাগ?

বাগ নয়, নকশার একটি সরাসরি পরিণতি। মূল কথা: def নিজেই একটি চলমান statement. Python যখন def add(x, items=[]): লাইনে পৌঁছায়, তখন একটি function object তৈরি হয়, আর default মানের expression — এখানে [] — ঠিক তখনই, একবার হিসাব হয়। তৈরি হওয়া list-টি function object-এর ভেতরে জমা থাকে; দেখতে চাইলে add.__defaults__. প্রতিবার argument ছাড়া ডাকলে সেই একই list ব্যবহার হয়।

তাই প্রথম ডাকে list-এ 1, দ্বিতীয় ডাকে 2 — দুটি একই list-এ জমে, ফল [1, 2]. int বা str default-এ এই সমস্যা দেখা যায় না, কারণ সেগুলো immutable — “বদলানো” মানে নতুন object, পুরোনো default অক্ষত থাকে।

কেন Python প্রতি ডাকে default নতুন করে হিসাব করে না? কয়েকটি কারণ। এক, সরলতা ও গতি — প্রতি ডাকে expression চালানো ধীর। দুই, ধারাবাহিকতা — default expression-এ যেকোনো কিছু থাকতে পারে (timeout=DEFAULT_TIMEOUT), আর def-এর সময়ের মান ধরা একটি স্পষ্ট নিয়ম। তিন, কখনো কখনো এই আচরণ ইচ্ছা করেই ব্যবহার হয় — পুরোনো কোডে def fib(n, _cache={}) দিয়ে সহজ cache বানানো হতো (আজ functools.cache ভালো উপায়)।

প্রচলিত সমাধান হলো None-কে sentinel হিসেবে ব্যবহার করা:

  • def add(x, items=None): তারপর ভেতরে if items is None: items = [] — এখন প্রতি ডাকে নতুন list.
  • dataclass-এ একই সমস্যা ঠেকাতে field(default_factory=list); list, dict বা set-এর মতো mutable default সরাসরি দিলে dataclass class তৈরির সময়েই ValueError দেয়।

linter-ও এটি ধরে: ruff-এর B006 নিয়ম (flake8-bugbear থেকে নেওয়া) mutable default দেখলে সতর্ক করে। এই প্রশ্নটি এত পরিচিত যে Python-এর অফিসিয়াল FAQ-তেও আলাদা প্রশ্ন হিসেবে আছে — “Why are default values shared between objects?”

মূল উপলব্ধি: default মান তৈরি হয় def চলার সময়, একবার — প্রতিটি ডাকে নয়। mutable default তাই সব ডাকে ভাগ হয়; সমাধান None sentinel বা dataclass-এর default_factory.

প্র ০৪shallow copy আর deep copy-র পার্থক্য কী? b = a, a[:], copy.copy, copy.deepcopy — কোনটি কী করে?

চারটির পার্থক্য বোঝার চাবি একটি প্রশ্ন: কোন স্তর পর্যন্ত নতুন object তৈরি হলো? ধরুন a = [[1], [2]] — একটি বাইরের list, ভেতরে দুটি list.

  • b = a — কোনো কপি নয়। একই বাইরের list-এ নতুন নাম (alias). b.append(...) করলে a-তেও দেখা যায়।
  • a[:], list(a), a.copy(), copy.copy(a) — shallow copy. বাইরের list নতুন, কিন্তু তার ঘরগুলো সেই একই ভেতরের list দেখায়। তাই b.append([3]) a-কে বদলায় না, কিন্তু b[0].append(9) a-কেও বদলায়।
  • copy.deepcopy(a) — deep copy. পুনরাবৃত্তিভাবে (recursively) প্রতিটি স্তরের mutable object নতুন করে বানায়। b আর a-এর মধ্যে আর কোনো ভাগাভাগি থাকে না।

তাহলে সব সময় deepcopy নয় কেন? প্রথমত খরচ — বড়, গভীর গঠনে deepcopy ধীর আর মেমরি-ভারী। দ্বিতীয়ত, সব জিনিস কপি করা অর্থপূর্ণ নয়: খোলা ফাইল, network connection, lock — এগুলো deepcopy করা যায় না বা করা উচিত নয়। তৃতীয়ত, অনেক সময় ভাগাভাগিটাই চাওয়া।

deepcopy একটি সূক্ষ্ম সমস্যাও সমাধান করে। ধরুন একটি list নিজেকেই ধারণ করে (x = []; x.append(x)), বা দুটি জায়গায় একই ভেতরের object আছে। সরল recursive কপি অসীম লুপে পড়ত, বা একটি object দুবার কপি হয়ে ভাগাভাগির গঠন হারাত। deepcopy একটি memo dictionary রাখে — কোন object ইতিমধ্যে কপি হয়েছে — তাই প্রতিটি object একবারই কপি হয় আর মূলের গঠন অক্ষত থাকে। নিজের class-এ কপির নিয়ম বদলাতে __copy__ আর __deepcopy__ লেখা যায়।

immutable object-এ এই প্রশ্ন প্রায় ওঠেই না: tuple-এর সব উপাদান immutable হলে copy.copy আর copy.deepcopy দুটিই সাধারণত সেই একই tuple ফেরত দেয় — বদলানোই যায় না, তাই কপির দরকার নেই।

মূল উপলব্ধি: = কপি নয়; shallow copy শুধু বাইরের স্তর নতুন করে, ভেতরের object ভাগ করে; deep copy সব স্তর নতুন করে আর memo দিয়ে চক্র সামলায়। nested mutable ডেটা আলাদা করতে হলে তবেই deepcopy.

প্র ০৫GIL কী, কেন আছে, আর “Python-এ thread দিয়ে কোনো লাভ নেই” — কথাটি কতটা ঠিক?

GIL (Global Interpreter Lock) হলো CPython-এর একটি lock: যেকোনো মুহূর্তে একটি process-এর মাত্র একটি thread Python bytecode চালাতে পারে। ১০টি thread থাকলেও Python কোড চালানোর পালা একবারে একজনের।

কেন? CPython মেমরি সামলায় reference counting দিয়ে — প্রতিটি object-এ একটি গণনা, কতগুলো নাম তাকে দেখায়। দুটি thread একসাথে একই গণনা বাড়ালে-কমালে ভুল হিসাব হতো, আর object অসময়ে মুছে যেত বা কখনো মুছত না। প্রতিটি object-এ আলাদা lock দিলে single-thread প্রোগ্রাম (যা ছিল বেশিরভাগ) অনেক ধীর হতো। তাই ১৯৯০-এর দশকে thread সমর্থন যোগ করার সময় একটিই বড় lock বেছে নেওয়া হয়েছিল — সরল, single-thread-এ দ্রুত, আর C extension লেখা সহজ।

“thread দিয়ে কোনো লাভ নেই” — অর্ধসত্য। ফাইল পড়া, network-এর উত্তরের অপেক্ষা, time.sleep-এর মতো blocking I/O-র সময় CPython GIL ছেড়ে দেয়। তাই I/O-bound কাজে (একসাথে ৫০টি ওয়েবপেজ নামানো) thread দারুণ কাজ করে। NumPy-র মতো C লাইব্রেরিও ভারী হিসাবের সময় GIL ছাড়ে। লাভ হয় না শুধু বিশুদ্ধ-Python CPU-bound কাজে — সেখানে multiprocessing বা ProcessPoolExecutor, যেখানে প্রতিটি process-এর নিজের GIL.

GIL সরানোর চেষ্টা পুরোনো। ১৯৯০-এর দশকের শেষে Greg Stein-এর একটি “free threading” patch-এ GIL সরানো গিয়েছিল, কিন্তু single-thread কোড উল্লেখযোগ্যভাবে ধীর হয়ে যায়, তাই সেটি গৃহীত হয়নি; ২০১৬ সালে শুরু হওয়া Larry Hastings-এর “Gilectomy” প্রকল্পও একই সমস্যায় থেমে যায়। শেষে Sam Gross-এর কাজ থেকে আসা PEP 703 (২০২৩ সালে গৃহীত) নতুন কৌশলে — biased reference counting, immortal object ইত্যাদি — খরচ অনেক কমায়। Python 3.13-এ পরীক্ষামূলক free-threaded build এলো, আর 3.14-এ এটি আনুষ্ঠানিকভাবে সমর্থিত (PEP 779), তবে এখনো আলাদা build, ডিফল্ট নয়। ডিফল্ট build-এ GIL আছে।

একটি সূক্ষ্ম কথা: GIL থাকলেও thread-safe হওয়ার নিশ্চয়তা নেই। count += 1 কয়েকটি bytecode নির্দেশ; দুই নির্দেশের মাঝে thread বদলে গেলে update হারায়। তাই ভাগ করা ডেটায় threading.Lock লাগে।

মূল উপলব্ধি: GIL একবারে একটি thread-কে bytecode চালাতে দেয় — I/O-bound কাজে thread উপকারী, CPU-bound কাজে process. free-threaded build 3.14-এ সমর্থিত, কিন্তু ডিফল্ট নয়; আর GIL কখনো lock-এর বিকল্প নয়।

প্র ০৬generator আর list-এর পার্থক্য কী? generator সব সময় ভালো হলে list কেন রাখা হয়?

list সব মান আগেই হিসাব করে মেমরিতে রাখে। generator কোনো মান আগে বানায় না — প্রতিবার next() চাইলে ঠিক একটি মান তৈরি করে দেয়, তারপর yield-এর জায়গায় থেমে অপেক্ষা করে। একে বলে lazy evaluation.

এর তিনটি সুবিধা। এক, মেমরি: sum(x * x for x in range(10**8)) প্রায় কোনো মেমরি ছাড়াই চলে, অথচ একই কাজে list বানালে কয়েক GB লাগত। দুই, অসীম বা অজানা দৈর্ঘ্য: itertools.count(), বা একটি বিশাল log ফাইলের লাইন একটি একটি করে পড়া। তিন, pipeline: একটি generator আরেকটিকে খাওয়ায় — পড়া, ছাঁকা, রূপান্তর — প্রতিটি মান পুরো শিকল পার হয়, মাঝখানে কোনো বড় list তৈরি হয় না।

কিন্তু দাম আছে, আর MCQ-র ফাঁদ এখানেই:

  • একবারই চলে: generator শেষ (exhausted) হলে আবার শুরু হয় না। sum(g), sum(g) দেয় 5 0. আবার চাইলে নতুন generator বানাতে হয়।
  • len আর ইনডেক্স নেই: len(g) বা g[0] TypeError — কত মান আছে, তা না চালিয়ে জানা যায় না।
  • ছোট ডেটায় ধীর হতে পারে: প্রতিটি মানের জন্য থামা-চালু হওয়ার খরচ আছে; কয়েকশো উপাদানে list comprehension প্রায়ই দ্রুত।

ইতিহাস: generator এসেছিল PEP 255 দিয়ে, Python 2.2-এ (২০০১), Icon ভাষার ধারণা থেকে অনুপ্রাণিত। Generator expression — বন্ধনীতে comprehension — এলো PEP 289 দিয়ে Python 2.4-এ। Python 3-এ range, map, zip, dict.items() সবই আর list ফেরত দেয় না — ভাষাটি নিজেই lazy-র দিকে সরে গেছে।

একটি প্রচলিত ভুল ধারণা: “range একটি generator.” না। range একটি lazy sequence: মান আগে বানায় না, কিন্তু len(range(10)), range(10)[3] আর একাধিকবার লুপ — সবই চলে, আর 5 in range(10) সরাসরি গণিত দিয়ে উত্তর দেয়, খুঁজে নয়।

মূল উপলব্ধি: generator মান চাহিদামতো একটি একটি করে বানায় — মেমরি বাঁচে, অসীম ক্রম সম্ভব; কিন্তু একবারই চলে, len বা ইনডেক্স নেই। বারবার ব্যবহার বা ইনডেক্স দরকার হলে list.

শেষ ধাপ — ক্রম সাজান আর স্কোর দেখুন

উপরের সব MCQ শেষ করলে নিচে আপনার মোট স্কোর দেখাবে — প্রথম চেষ্টায় কয়টি ঠিক হয়েছে।

ক্রমএকটি প্রোগ্রামে প্রথমবার import shop চললে Python যা করে, ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔍 sys.modules-এ shop আগে থেকে আছে কি না দেখা
  2. 🗺️ sys.path-এর ফোল্ডারগুলোতে shop খোঁজা (finder)
  3. 📦 একটি নতুন, খালি module object তৈরি করা
  4. 📝 module object-টিকে sys.modules-এ নিবন্ধন করা
  5. ▶️ module-এর কোড ওপর থেকে নিচে একবার চালানো
  6. 🏷️ import করা ফাইলে shop নামটি সেই module-এ বাঁধা

শব্দকোষ

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

মনে রাখুন

  1. আগে দুটি প্রশ্ন: কোড থাকলে আউটপুট খোঁজার আগে জিজ্ঞেস করুন — “এটি কি বৈধ Python?” আর “চললে কোন লাইনে কোন exception?” অনেক কঠিন প্রশ্নের উত্তর এখানেই মেলে, আর exception-এর নাম হুবহু জানতে হয়।
  2. বাদ দিন, খুঁজবেন না: প্রতিটি অপশনের পেছনের নিয়ম যাচাই করুন; ভুল অপশন দেখেই বোঝা যায় প্রশ্নটি কোন ভুল ধারণা ধরতে চায় — যেমন a += [4] বনাম a = a + [4].
  3. নাম object দেখায়, কপি করে না: alias, mutable default, [[0] * 2] * 2, shallow copy আর closure-এর late binding — সব ফাঁদের মূল এই একটি কথা।
  4. সংখ্যার তিন ফাঁদ: // floor নেয় (−৭ // ২ = −৪), round জোড়ের দিকে যায়, আর float-এ 0.1 + 0.2 != 0.3.
  5. সংস্করণ ছাড়া উত্তর নেই: match (3.10), finally-র return-এ warning (3.14), float-এর sum (3.12), free-threaded build (3.13–3.14) — ভুলে নম্বর কাটলে হিসাব করে আন্দাজ করুন; AI-এর বানানো প্রশ্ন চালিয়ে যাচাই করুন।

পরের পর্বে: MCQ-তে চারটি অপশন থেকে বাছতে হয়। লিখিত আর মৌখিক (ভাইভা) পরীক্ষায় কোনো অপশন নেই — নিজের ভাষায় সংজ্ঞা, পার্থক্য আর উদাহরণ দিতে হয়। পর্ব ৫৪-এ সেই প্রশ্নগুলো আর উত্তর সাজানোর কৌশল।

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

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