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

ভ্যারিয়েবল ও টাইপ — নাম, object আর dynamic typing

Variables & Types — Names, Objects and Dynamic Typing

Taught in Bangla, this lesson explains Python variables as names bound to objects: naming rules, what reassignment really does, multiple and chained assignment, the five basic types, converting between types, dynamic versus static typing, type hints and constants.

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

  • Assignment আসলে কী করে, আর কেন x = x + 1 গণিতে ভুল হলেও Python-এ একদম ঠিক
  • নাম রাখার নিয়ম, যেসব keyword নাম হিসেবে চলে না, আর ভালো নাম বনাম খারাপ নাম
  • একসাথে কয়েকটি assignment, এক লাইনে swap, আর x = y = ... কখন নিরাপদ
  • Python-এর পাঁচটি মৌলিক type, type() ও isinstance(), আর None-এর আসল অর্থ
  • Type conversion, truthiness, আর dynamic বনাম static typing-এর trade-off

১ · ভ্যারিয়েবল কী — মানের গায়ে একটি নাম

আগের পর্বে আপনি একটি বড় সত্য দেখেছেন: Python-এ প্রতিটি মান একটি object, আর নাম হলো সেই object-এর গায়ে লাগানো একটি লেবেল। এই পর্বে সেই লেবেল-ছবি হাতে নিয়েই আমরা রোজকার প্রোগ্রামিংয়ে ঢুকব। প্রশ্নটা খুব সাধারণ: একটি মান পরে আবার ব্যবহার করতে চাইলে কী করবেন?

ধরুন একটি চায়ের দোকানে আপনি হিসাব রাখছেন। আজ ৪৭ কাপ চা বিক্রি হলো, প্রতি কাপ ১০ টাকা। আপনি প্রোগ্রামে লিখতে পারেন print(47 * 10). কিন্তু একটু পরে যদি জানতে চান “কাপ প্রতি লাভ কত?” — তখন আবার 47 লিখতে হবে। কাল সংখ্যাটা বদলালে সব জায়গায় খুঁজে খুঁজে বদলাতে হবে। এর সমাধান: মানটিকে একটি নাম দিন।

cups_sold = 47
price_per_cup = 10
total = cups_sold * price_per_cup

এখানে cups_sold, price_per_cup, total — তিনটিই ভ্যারিয়েবল. সহজ ভাষায়: ভ্যারিয়েবল হলো একটি নাম, যা আপনি একটি মানের গায়ে লাগান, যাতে পরে সেই নাম দিয়ে মানটি আবার পান। নামটি মানুষের জন্য অর্থ বহন করে — 47 দেখে কিছু বোঝা যায় না, কিন্তু cups_sold দেখে সবাই বোঝে এটা বিক্রি হওয়া কাপের সংখ্যা।

= মানে “সমান” নয়

গণিতের ক্লাসে = মানে দুই পাশ সমান। Python-এ = একটি কাজের নির্দেশ, যার নাম assignment. এটি সবসময় দুই ধাপে চলে:

  1. আগে ডান দিক: Python ডান পাশের expression হিসাব করে একটি object পায় (বা বানায়)।
  2. তারপর বাম দিক: বাম পাশের নামটি সেই object-এর গায়ে লাগিয়ে দেয়। নামটি আগে অন্য কোথাও লাগানো থাকলে খুলে এনে নতুন জায়গায় লাগায়।

এই ক্রম বুঝলে একটি “অসম্ভব” লাইনও সহজ হয়ে যায়: total = total + 5. গণিতে এর কোনো সমাধান নেই। কিন্তু Python-এ এর অর্থ পরিষ্কার — “এখন total যে মান দেখায়, তার সাথে ৫ যোগ করো; যে নতুন object পেলে, total নামটি সেটির গায়ে সরিয়ে নাও।” সমান কিনা যাচাই করতে Python-এ আলাদা চিহ্ন আছে: ==. দুটি গুলিয়ে ফেলা নতুনদের সবচেয়ে সাধারণ ভুলগুলোর একটি; অপারেটর নিয়ে বিস্তারিত পর্ব ০৮-এ।

ক্রিকেট মাঠের স্কোরবোর্ডের কথা ভাবুন। বোর্ডে লেখা থাকে “রান”, আর তার পাশে একটি সংখ্যা। ব্যাটসম্যান চার মারলে স্কোরার আগে হিসাব করেন — আগের রান যোগ ৪ — তারপর নতুন সংখ্যার কার্ডটি “রান” লেখার পাশে ঝুলিয়ে দেন। “রান” লেখাটি একই থাকে, পাশের কার্ড বদলায়। runs = runs + 4 ঠিক এমন: নাম একই, যে মান সে দেখায় সেটি নতুন।

মূল ধারণা

Python-এ ভ্যারিয়েবল তৈরি করার আলাদা কোনো ঘোষণা নেই। প্রথমবার কোনো নামে assignment করলেই নামটির জন্ম। আর assignment-এর আগে নামটি ব্যবহার করলে পাবেন NameError — যা আপনি পর্ব ০৪-এ error পড়ার সময় দেখেছেন।

২ · নাম রাখার নিয়ম — যা চলে, যা চলে না

ভ্যারিয়েবল, ফাংশন, class — Python-এ যা কিছুর নাম দেন, তার প্রত্যেকটিকে বলে identifier. Identifier-এর জন্য কিছু কঠিন নিয়ম আছে, যা না মানলে প্রোগ্রাম চলবেই না (SyntaxError). আর কিছু ভদ্রতার নিয়ম আছে, যা না মানলে প্রোগ্রাম চলবে, কিন্তু অন্যরা পড়তে কষ্ট পাবে।

কঠিন নিয়ম (না মানলে SyntaxError)

  • নাম শুরু হতে হবে একটি অক্ষর বা _ (underscore) দিয়ে, সংখ্যা দিয়ে নয়।
  • এরপর অক্ষর, সংখ্যা আর _ চলবে। ফাঁকা জায়গা, -, @, $ চলবে না।
  • বড় হাত আর ছোট হাতের অক্ষর আলাদা — Python case-sensitive. তাই Roll, roll, ROLL তিনটি আলাদা নাম।
  • Python-এর নিজস্ব keyword নাম হিসেবে চলবে না।
নামচলবে?কারণ
student_nameহ্যাঁঅক্ষর আর underscore — আদর্শ
roll2হ্যাঁসংখ্যা মাঝে বা শেষে চলে
_tempহ্যাঁunderscore দিয়ে শুরু চলে (প্রথায় “ভেতরের ব্যবহারের জন্য”)
2nd_rollনাসংখ্যা দিয়ে শুরু
my-nameনাPython এটিকে my বিয়োগ name পড়ে
total amountনানামের মাঝে ফাঁকা জায়গা
classনাkeyword

Keyword — Python-এর সংরক্ষিত শব্দ

কিছু শব্দ Python নিজের ব্যাকরণের জন্য রেখে দিয়েছে — যেমন if, for, def, class, return, True, False, None. এগুলো নাম হিসেবে দিলে SyntaxError. পুরো তালিকা Python নিজেই দেখাতে পারে:

import keyword
print(len(keyword.kwlist))     # Python 3.14-এ 35
print(keyword.softkwlist)      # ['_', 'case', 'match', 'type']

দ্বিতীয় তালিকাটি soft keyword-এর — এগুলো শুধু নির্দিষ্ট জায়গায় keyword হিসেবে কাজ করে (যেমন match Python 3.10 থেকে, পর্ব ১১-এ দেখবেন), বাকি জায়গায় সাধারণ নাম হিসেবেও চলে। পুরোনো কোড যাতে না ভাঙে, সেজন্যই এই ব্যবস্থা। তবু নিজের ভ্যারিয়েবলের নাম match বা type না রাখাই ভালো।

ভদ্রতার নিয়ম — PEP 8 আর snake_case

Python সম্প্রদায়ের লেখার নিয়মের বইয়ের নাম PEP 8. এর মূল কথা: কোড লেখা হয় একবার, পড়া হয় বহুবার। নাম নিয়ে PEP 8-এর পরামর্শ:

🐍 snake_case

ভ্যারিয়েবল আর ফাংশনের নাম ছোট হাতের অক্ষরে, শব্দের মাঝে underscore. এটাই snake_case.

monthly_salary, is_present

🏛️ PascalCase

Class-এর নাম প্রতিটি শব্দের প্রথম অক্ষর বড় হাতে, কোনো underscore নেই। পর্ব ২৯-এ লাগবে।

StudentRecord

📌 UPPER_CASE

যে মান প্রোগ্রামে বদলাবে না বলে ঠিক করেছেন (ধ্রুবক), তার নাম সব বড় হাতে। অংশ ৭-এ বিস্তারিত।

VAT_RATE, MAX_LOGIN_TRY

ভালো নাম বনাম খারাপ নাম

নিয়ম মানলেই নাম ভালো হয় না। ভালো নাম পড়েই বোঝা যায় ভেতরে কী আছে। তুলনা করুন:

খারাপ নামসমস্যাভালো নাম
x, dকিছুই বলে নাdiscount_percent
data, tempসব কিছুই data; কী data?exam_marks
taka, chatro_namবাংলা শব্দ ইংরেজি অক্ষরে — অন্যরা বুঝবে না, বানানও এক নয়amount_bdt, student_name
flagকোন প্রশ্নের হ্যাঁ/না?is_paid, has_discount
l, Oঅনেক ফন্টে 1 আর 0-এর মতো দেখায়length, order

কয়েকটি ছোট কৌশল কাজে লাগে। bool মানের নাম প্রশ্নের মতো রাখুন — is_present, has_paid. একক রাখুন নামে — weight_kg, delay_seconds. আর ছোট লুপের ভেতরে i বা এক লাইনের হিসাবে x চলে, কিন্তু যে নাম বিশ লাইন পরেও ব্যবহার হবে, সেটি পূর্ণ শব্দে লিখুন।

বাংলা অক্ষরে নাম? Python 3 থেকে identifier-এ Unicode অক্ষর চলে, তাই দাম = 50 সত্যিই কাজ করে। কিন্তু করবেন না। কারণ: বাংলা কি-বোর্ডে টাইপ করা ধীর; একই শব্দের বানান নানা রকম হয় (যেমন “বাড়ি” আর “বাড়ী”) — অর্থ এক, কিন্তু Python-এর কাছে দুটি আলাদা নাম; library, টুল আর বিদেশি সহকর্মী সবাই ইংরেজি নাম আশা করেন। বাংলা রাখুন লেখায় (string-এ, comment-এ), নামে নয়।

৩ · নতুন মান দিলে কী হয় — নাম সরে, object বদলায় না

একটি নামে দ্বিতীয়বার assignment করাকে বলে reassignment. এখানেই Python-এর লেবেল-ছবি সবচেয়ে কাজে লাগে। নিচের চারটি লাইনে খেয়াল রাখুন — কোন নাম কোন object দেখাচ্ছে, আর কোন object-কে আর কেউ দেখাচ্ছে না। “পরে” চাপুন:

1x = 52x = "পাঁচ"3y = x4del x
নাম → object (সবুজ লাইনটি এইমাত্র চলেছে)
x
y
5 (int)
'পাঁচ' (str)
  1. x = 5 — object আগে, নাম পরে। Python একটি int object (মান 5) পায়, তারপর x লেবেলটি তার গায়ে লাগায়। এই মুহূর্তে type(x) বললে উত্তর int — কিন্তু আসলে প্রশ্নটা হলো “x যে object দেখায়, তার type কী?”
  2. x = "পাঁচ" — লেবেল সরে গেল। একটি নতুন str object তৈরি হলো, আর x লেবেলটি 5-এর গা থেকে খুলে এখানে লাগল। খেয়াল করুন: 5 নিজে string হয়ে যায়নি — কোনো object কখনো নিজের type বদলায় না। বদলেছে শুধু নামের অবস্থান। 5-কে এখন আর কোনো নাম দেখায় না, তাই আমাদের প্রোগ্রাম থেকে তাকে আর পাওয়ার উপায় নেই (unreachable). সাধারণ object হলে reference counting তাকে মুছে দিত; ছোট int-গুলো অবশ্য CPython নিজের জন্য সবসময় রেখে দেয় — তবে সেটা ভেতরের বিষয়।
  3. y = x — দ্বিতীয় লেবেল। ডান দিকের x মানে “x যে object দেখায়”। তাই y লাগল সেই একই 'পাঁচ'-এর গায়ে। নতুন string তৈরি হয়নি। পর্ব ০৫-এ দেখা alias-এর ধারণা এটাই।
  4. del x — নাম মোছে, object নয়। del শুধু x নামটি মুছে দেয়। 'পাঁচ' টিকে আছে, কারণ y এখনো তাকে দেখায়। এখন print(x) লিখলে NameError, কিন্তু print(y) ঠিকঠাক “পাঁচ” ছাপবে।

দ্বিতীয় ধাপটিই এই পর্বের কেন্দ্রীয় ধারণা: Python-এ type থাকে মানের (object-এর) গায়ে, নামের গায়ে নয়। নাম শুধু একটি লেবেল; যেকোনো type-এর object-এ লাগানো যায়, আর যেকোনো সময় সরানো যায়। একে বলে dynamic typing. এটা অনেক নমনীয় — কিন্তু শৃঙ্খলা না থাকলে বিপদও ডাকে, যা অংশ ৭ ও ৮-এ দেখবেন।

নামের কোনো type নেই — type থাকে object-এর
Assignment = লেবেল লাগানো বা সরানো, object বদলানো নয়

del — নাম মুছে ফেলা

del name একটি নামকে বর্তমান জায়গা থেকে সরিয়ে দেয়। রোজকার ছোট প্রোগ্রামে এর দরকার খুব কম পড়ে — নাম আপনাআপনি অপ্রয়োজনীয় হয়ে যায়। তবে বড় ডেটা (যেমন বিশাল একটি list) দিয়ে কাজ শেষ হলে নামটি মুছে দিলে, আর কেউ সেটি না দেখালে, মেমরি তাড়াতাড়ি ফেরত যায়। মনে রাখুন, del object “ধ্বংস” করার আদেশ নয় — শুধু একটি লেবেল খুলে ফেলা।

৪ · এক লাইনে কয়েকটি assignment — swap আর chained

একসাথে কয়েকটি নাম

Python-এ এক লাইনে কয়েকটি নামে মান দেওয়া যায়: name, roll = "রাফি", 17. একে বলে multiple assignment. ডান পাশে যতগুলো মান, বাম পাশে ঠিক ততগুলো নাম থাকতে হবে; না মিললে ValueError (“too many values to unpack” বা “not enough values”). ভেতরে আসলে দুটি কাজ ঘটে — ডান পাশের মানগুলো দিয়ে একটি tuple বানানো (packing), তারপর সেটি খুলে একেকটি মান একেকটি নামে দেওয়া (unpacking).

Swap — তৃতীয় ভ্যারিয়েবল ছাড়া অদলবদল

C বা অনেক পুরোনো ভাষায় দুটি ভ্যারিয়েবলের মান অদলবদল করতে একটি অস্থায়ী তৃতীয় ভ্যারিয়েবল লাগে: temp = a, a = b, b = temp. দুই গ্লাসের শরবত অদলবদল করতে তৃতীয় একটি খালি গ্লাস লাগে — যুক্তিটা এমন। Python-এ এক লাইনেই হয়: a, b = b, a. কীভাবে? ধাপে ধাপে দেখুন:

1a, b = 10, 202a, b = b, a3print(a, b)
নাম → object, আর ক্ষণস্থায়ী tuple
a
b
ডান দিকের tuple
  1. শুরুর অবস্থা। Multiple assignment-এ a লাগল 10-এ, b লাগল 20-এ।
  2. আগে পুরো ডান দিক — packing. Assignment-এর প্রথম নিয়ম মনে আছে? আগে ডান দিক। b, a হিসাব করে Python পায় 20 আর 10, আর এদের নিয়ে একটি tuple (20, 10) বানায়। এই মুহূর্তে কোনো নাম এখনো সরেনি — দুটি মানই নিরাপদে tuple-এর ভেতরে ধরা আছে। শরবতের উদাহরণে এই tuple-ই সেই “তৃতীয় গ্লাস”, শুধু Python নিজেই সেটা সামলায়।
  3. Unpacking — প্রথম মান প্রথম নামে। এবার বাম দিক: tuple-এর প্রথম মান 20, তাই a লেবেলটি 20-এ সরল।
  4. দ্বিতীয় মান দ্বিতীয় নামে। b সরল 10-এ। অদলবদল শেষ। যদি Python ডান দিক পুরোটা আগে না হিসাব করত, তাহলে a সরার পর b-ও 20 পেত — দুটোই 20 হয়ে যেত। “আগে ডান দিক” নিয়মটিই swap-কে নিরাপদ করে।
  5. print(a, b) → 20 10. কাজ শেষে tuple-টিকে আর কেউ দেখায় না, সেটি চলে যায়। (CPython আসলে এই ছোট swap-এ tuple না বানিয়ে সরাসরি অদলবদলের একটি bytecode নির্দেশ ব্যবহার করে — কিন্তু ফলাফল হুবহু একই, আর ধারণা বোঝার জন্য tuple-এর ছবিটাই সঠিক।)

একই কৌশলে তিনটি মানও ঘোরানো যায়: a, b, c = b, c, a. Tuple আর unpacking-এর আরও শক্তিশালী রূপ (যেমন first, *rest = ...) দেখবেন পর্ব ১৫-এ।

Chained assignment — x = y = 0

x = y = 0 লিখলে x আর y দুটোই একই object 0-এর গায়ে লাগে। একে বলে chained assignment. সংখ্যা, string বা None-এর ক্ষেত্রে এটা পুরোপুরি নিরাপদ, কারণ এরা immutable — তৈরির পর ভেতরটা বদলানো যায় না। পরে x = x + 1 করলে নতুন object 1 তৈরি হয়ে x সেখানে সরে যায়; y 0-তেই থাকে।

List-এর বেলায় সাবধান। morning = evening = [] লিখলে দুটি নাম একটিই list দেখায়। morning.append("রহিম") করলে evening-এও “রহিম” দেখা যাবে — কারণ list হলো mutable, আর এখানে list মাত্র একটি। দুটি আলাদা খালি list চাইলে লিখুন morning, evening = [], []. এই ফাঁদের পুরো ব্যাখ্যা আর copy-র নিয়ম পর্ব ১৯-এ।

৫ · Python-এর পাঁচটি মৌলিক type

Type হলো object-এর “জাত”। Type ঠিক করে object-টি দিয়ে কী কী করা যায় — সংখ্যা যোগ-গুণ করা যায়, লেখা জোড়া লাগানো বা বড় হাতে বদলানো যায়। Python-এ শুরুতে যে পাঁচটি type সবচেয়ে বেশি দেখবেন:

Typeকী রাখেউদাহরণবাস্তব ব্যবহার
intপূর্ণসংখ্যা, যত বড়ই হোক17, -3, 1_000_000রোল নম্বর, কাপের সংখ্যা
floatদশমিক সংখ্যা (আনুমানিক)4.75, 0.15, 2e3GPA, ওজন, শতাংশ
boolশুধু True বা FalseTrueউপস্থিত কি না, টাকা দেওয়া হয়েছে কি না
strলেখা (Unicode অক্ষরের সারি)"ঢাকা", 'A+'নাম, ঠিকানা, মোবাইল নম্বর
NoneTypeশুধু একটি মান: NoneNone“এখনো কোনো মান নেই”

কয়েকটি তথ্য আগেই জেনে রাখুন। Python-এর int-এর কোনো সর্বোচ্চ সীমা নেই — মেমরি যতক্ষণ আছে, সংখ্যা তত বড় হতে পারে (C-র মতো 32 বা 64 bit-এ আটকে যায় না)। float কিন্তু আনুমানিক, তাই 0.1 + 0.2 হুবহু 0.3 হয় না — এই রহস্য আর টাকার হিসাবে কী করবেন, তা পরের পর্বে (পর্ব ০৭)। আর মোবাইল নম্বর "01711000000" সবসময় str রাখুন, int নয় — শুরুর 0 হারিয়ে যাবে, আর মোবাইল নম্বর দিয়ে তো কেউ যোগ-বিয়োগ করে না। নিয়মটা সহজ: যা দিয়ে হিসাব করবেন, তা সংখ্যা; যা শুধু পরিচয়, তা লেখা।

type() আর isinstance()

কোনো নাম এখন কোন type-এর object দেখাচ্ছে, তা জানতে type(x). আর “এটা কি এই type-এর?” — হ্যাঁ/না প্রশ্নের জন্য isinstance(x, int). দুটির পার্থক্য সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ: isinstance উত্তরাধিকারও (subclass) গোনে। তাই যাচাইয়ের জন্য সাধারণত isinstance ব্যবহার করুন, type(x) == int নয়। কেন, তা এখনই bool-এর উদাহরণে দেখবেন।

একটি কৌতূহল — bool আসলে int-এর সন্তান

Python-এ bool হলো int-এর subclass. True আচরণ করে 1-এর মতো, False 0-এর মতো। তাই True + True হলো 2, আর isinstance(True, int) হলো True. এটা শুনতে অদ্ভুত, কিন্তু কাজেও লাগে: sum(attendance) লিখলে একটি True/False-এর list থেকে সরাসরি উপস্থিত ছাত্রের সংখ্যা পাওয়া যায়। কেন এমন নকশা — তা ভাবনার প্রশ্ন ২-এ।

None — “এখনো কোনো মান নেই”

ধরুন একটি ছাত্রের তথ্য রাখছেন, কিন্তু অভিভাবকের ফোন নম্বর এখনো জানা নেই। 0 দেবেন? তাহলে মনে হবে নম্বরটা 0. খালি লেখা "" দেবেন? তাহলে মনে হবে নম্বরটা জানা, কিন্তু ফাঁকা। সঠিক উত্তর None: “এই জায়গায় এখনো কোনো মান নেই।” None একটি সত্যিকারের object, পুরো প্রোগ্রামে মাত্র একটিই আছে। তাই এটি যাচাই করার প্রচলিত উপায় x is None (পর্ব ০৫-এ দেখা identity তুলনা), x == None নয়। যে ফাংশন কিছু return করে না, সে-ও আসলে None ফেরত দেয় — print()-এর ফলাফল None; পর্ব ১৩-এ বিস্তারিত।

এবার একটি ছাত্রের রেকর্ড দিয়ে সব একসাথে দেখুন। কোডটি চালান:

name = "রাফি আহমেদ"
roll = 17
gpa = 4.75
is_present = True
guardian_phone = None      # এখনো জানা নেই

for value in (name, roll, gpa, is_present, guardian_phone):
    print(type(value).__name__, value)

print(isinstance(roll, int), isinstance(gpa, int))
print(True + True, isinstance(True, int))

a, b = 10, 20
a, b = b, a                # swap — তৃতীয় ভ্যারিয়েবল লাগে না
print(a, b)

score = 90
score = "A+"               # একই নাম, এখন অন্য type-এর object
print(score, type(score).__name__)

প্রথম পাঁচ লাইনের আউটপুট: str রাফি আহমেদ, int 17, float 4.75, bool True, NoneType None. type(value).__name__ শুধু type-এর নামটি দেয়, যাতে আউটপুট পরিষ্কার থাকে (লুপের বিস্তারিত পর্ব ১২-এ)। এরপর True False — roll একটি int, কিন্তু gpa নয়। তারপর 2 True: True + True হলো 2, আর True সত্যিই একটি int. Swap-এর পর 20 10. শেষ লাইন A+ str — একই নাম score আগে একটি int দেখাচ্ছিল, এখন একটি str.

৬ · Type conversion — লেখা থেকে সংখ্যা, সংখ্যা থেকে লেখা

বাস্তব প্রোগ্রামে ডেটা প্রায়ই ভুল type-এ আসে। ব্যবহারকারী কি-বোর্ডে যা-ই লিখুন, input() সবসময় একটি str দেয় — এমনকি তিনি “250” লিখলেও। ফাইল, ওয়েব ফর্ম, SMS — সব জায়গা থেকেই আসে লেখা। তাই হিসাবের আগে লেখাকে সংখ্যায় বদলাতে হয়। একে বলে type conversion, আর এর জন্য type-এর নামগুলোকেই ফাংশনের মতো ডাকা হয়: int(), float(), str(), bool().

একটি মোবাইল রিচার্জ অ্যাপের কথা ভাবুন। ব্যবহারকারী রিচার্জের পরিমাণ লিখলেন “250”, আর অ্যাপ ৫০ টাকা বোনাস যোগ করে মোট দেখাবে। লেখা থেকে উত্তর পর্যন্ত যাত্রাটা দেখুন:

1text = input()2amount = int(text)3total = amount + 504msg = "মোট: " + str(total)5print(msg)
নাম → object (type সহ)
text
amount
total
msg
  1. input() সবসময় লেখা দেয়। ব্যবহারকারী 2, 5, 0 চাপলেন। text পেল '250' — তিনটি অক্ষরের একটি string, সংখ্যা নয়। এখন text + 50 লিখলে TypeError আসত, কারণ Python লেখা আর সংখ্যা নিজে থেকে মেশায় না। আর text + "50" লিখলে পেতেন '25050' — জোড়া লাগানো লেখা, যোগফল নয়!
  2. int(text) — নতুন object. int() লেখাটি পড়ে একটি নতুন int object 250 বানাল, আর amount তার গায়ে লাগল। খেয়াল করুন, text এখনো '250' (str) — conversion পুরোনো object বদলায় না, নতুন object বানায়।
  3. এবার হিসাব। দুটি int, তাই + মানে যোগ: 250 + 50 = 300. একই + চিহ্ন দুটি str পেলে জোড়া লাগায় — কাজটা ঠিক করে object-এর type, পর্ব ০৫-এ VM-এর ভেতরে যেমন দেখেছেন।
  4. str(total) — আবার লেখায়। “মোট: ” একটি string, তার সাথে + দিয়ে জুড়তে হলে ডান দিকটিও string হতে হবে। str(300) বানাল '300'. (পর্ব ১০-এ f-string শিখলে এই কাজ আরও সহজ হবে: f"মোট: {total}".)
  5. আউটপুট: মোট: 300. পুরো যাত্রা: লেখা → সংখ্যা → হিসাব → লেখা। বাস্তব প্রায় প্রতিটি প্রোগ্রামের সীমানায় এই পাইপলাইন থাকে — বাইরে থেকে আসে লেখা, ভেতরে চলে সংখ্যা, বাইরে যায় আবার লেখা।

যখন conversion ব্যর্থ হয়

int() শুধু এমন লেখা গ্রহণ করে, যা দেখতে একটি পূর্ণসংখ্যার মতো। দুই পাশের ফাঁকা জায়গা আর শুরুর +/- চলে (int(" -42 ") হলো -42), কিন্তু দশমিক বিন্দু চলে না:

>>> int("12.5")
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    int("12.5")
    ~~~^^^^^^^^
ValueError: invalid literal for int() with base 10: '12.5'

শেষ লাইনটি পড়ুন: ValueError মানে type ঠিক আছে (str দেওয়ারই কথা), কিন্তু মানটি গ্রহণযোগ্য নয়। “base 10” মানে সে দশভিত্তিক পূর্ণসংখ্যা খুঁজছিল। দশমিক লেখা থেকে পূর্ণসংখ্যা চাইলে দুই ধাপে যান: int(float("12.5")) দেয় 12 — খেয়াল করুন, এটি দশমিক অংশ কেটে ফেলে, পূর্ণসংখ্যায় রাউন্ড করে না। এর সাথে গুলিয়ে ফেলবেন না TypeError-কে, যা আসে যখন type-ই ভুল — যেমন "250" + 50. ব্যবহারকারীর ভুল ইনপুট সামলানোর নিয়ম (try/except) পর্ব ২৪-এ।

বাংলা পাঠকদের জন্য একটি বিশেষ তথ্য: int("১২") সত্যিই 12 দেয়, আর float("৩.৫") দেয় 3.5. Python 3-এর int() আর float() যেকোনো লিপির দশমিক অঙ্ক গ্রহণ করে — বাংলা, আরবি, দেবনাগরী — যতক্ষণ Unicode সেটিকে “decimal digit” (বিভাগ Nd) বলে চিহ্নিত করে। তাই বাংলায় লেখা ফর্ম থেকে আসা “৫০০” সরাসরি সংখ্যায় বদলানো যায়। তবে উল্টো দিকে যায় না: str(12) সবসময় ইংরেজি অঙ্কে '12' দেয়; বাংলা অঙ্কে দেখাতে নিজে বদলাতে হবে। আর সাবধান — "²" (superscript দুই) দেখতে অঙ্ক হলেও decimal digit নয়, তাই int("²") হলো ValueError.

Truthiness — সব মানই সত্য বা মিথ্যা হতে পারে

bool()-এর conversion একটু আলাদা। Python-এ যেকোনো মানকে “সত্য” বা “মিথ্যা” হিসেবে বিচার করা যায় — একে বলে truthiness. নিয়মটা মনে রাখার মতো সহজ: “শূন্য” বা “খালি” জিনিস মিথ্যা, বাকি সব সত্য। মিথ্যা হিসেবে বিচার হওয়া মানগুলোকে বলে falsy.

Falsy (bool() দেয় False)Truthy (bool() দেয় True)
0, 0.0-3, 0.001 — শূন্য ছাড়া যেকোনো সংখ্যা
"" (খালি string)" ", "0", "False" — অন্তত একটি অক্ষর আছে
[] (খালি list)[0] — একটি উপাদান আছে, সেটা 0 হলেও
None, FalseTrue

সবচেয়ে বড় ফাঁদ ডান কলামে: bool("False") হলো True! কারণ bool() লেখার অর্থ পড়ে না, শুধু দেখে লেখাটি খালি কি না। ধাপে ধাপে যাচাই করুন:

1bool(0)2bool(-3)3bool("")4bool("0")5bool([])6bool(None)
Python-এর প্রশ্ন
মানটি
শূন্য বা খালি?
  1. bool(0) → False. শূন্য সংখ্যা falsy. ব্যাংকের ব্যালান্স 0 হলে “ব্যালান্স আছে?” প্রশ্নের উত্তর “না” — যুক্তিটা স্বাভাবিক।
  2. bool(-3) → True. ঋণাত্মক হলেও শূন্য নয়, তাই truthy. ঋণাত্মক ব্যালান্সকে “আছে” ধরা হবে — আপনার প্রোগ্রামে তা ঠিক কি না, ভেবে দেখতে হবে।
  3. bool("") → False. কোনো অক্ষর নেই, খালি string. ব্যবহারকারী কিছু না লিখেই Enter চাপলে input() ঠিক এটাই দেয়।
  4. bool("0") → True. ফাঁদ! লেখাটিতে একটি অক্ষর আছে — “0” — তাই এটি খালি নয়। ইনপুট থেকে আসা “0”-কে মিথ্যা ধরতে চাইলে আগে int() দিয়ে সংখ্যায় বদলাতে হবে।
  5. bool([]) → False. খালি list falsy. কোনো অর্ডার নেই মানে “অর্ডার আছে?” — না। list নিয়ে বিস্তারিত পর্ব ১৪-এ।
  6. bool(None) → False. “কোনো মান নেই” স্বাভাবিকভাবেই মিথ্যা। এই পুরো টেবিল আসল কাজে লাগবে পর্ব ১১-এ, যখন if balance: জাতীয় শর্ত লিখবেন।

এবার রিচার্জ, conversion আর truthiness একসাথে চালিয়ে দেখুন। এই কোডের ইনপুট হিসেবে আগে থেকেই 250 দেওয়া আছে:

VAT_RATE = 0.15            # ধ্রুবক — নিয়ম মেনে বড় হাতের নাম

text = input()             # ব্যবহারকারী লিখলেন 250
print(type(text).__name__, repr(text))
print(text + "50")         # str + str = জোড়া লাগানো

amount = int(text)         # লেখা থেকে পূর্ণসংখ্যা
print(type(amount).__name__, amount + 50)

print(int("১২") + 1, float("৩.৫"), int(" 42 "))
print(int(float("12.5")), str(10) + " টাকা")
print(bool(0), bool(""), bool("0"), bool([]), bool(None))

price = 1000
vat = price * VAT_RATE
print(f"VAT {vat}, মোট {price + vat}")

ইনপুট হিসেবে 250 দেওয়া হয়েছে। প্রথম লাইন str '250' — repr() উদ্ধৃতিচিহ্নসহ দেখায়, যাতে বোঝা যায় এটি লেখা। তারপর 25050: দুটি string জোড়া লাগল, যোগ হলো না। int-এ বদলানোর পর int 300. পরের লাইন 13 3.5 42 — বাংলা “১২” থেকে 12, তার সাথে 1; বাংলা “৩.৫” থেকে 3.5; আর দুই পাশের ফাঁকা জায়গা বাদ দিয়ে 42. এরপর 12 10 টাকা — 12.5-এর দশমিক অংশ কেটে 12. Truthiness লাইনে False False True False False — শুধু "0" truthy. শেষে VAT 150.0, মোট 1150.0: int আর float মিশলে ফল float হয়, তাই .0. (টাকার হিসাবে float কেন ঝুঁকিপূর্ণ, পর্ব ০৭-এ দেখবেন।)

৭ · Dynamic বনাম static typing, type hint আর ধ্রুবক

C, C++, Java-র মতো ভাষায় ভ্যারিয়েবল তৈরির সময় type ঘোষণা করতে হয়: int age = 20;. এরপর age-এ লেখা রাখতে চাইলে compiler প্রোগ্রাম চালানোর আগেই আটকে দেয়। একে বলে static typing — type জানা থাকে কোড লেখার সময়, নামের সাথে বাঁধা। Python-এ type জানা যায় শুধু চলার সময়, আর তা থাকে object-এর সাথে — dynamic typing.

⚡ Dynamic typing-এর সুবিধা

কম লেখা, দ্রুত পরীক্ষা। REPL-এ যা খুশি চেষ্টা করা যায়। একই ফাংশন বিভিন্ন type-এর মানে কাজ করতে পারে। ছোট স্ক্রিপ্ট, ডেটা বিশ্লেষণ, prototype-এ সময় বাঁচে।

লেখার গতি

⚠️ Dynamic typing-এর দাম

Type-এর ভুল ধরা পড়ে শুধু সেই লাইন চললে — হয়তো গ্রাহকের সামনে, মাসখানেক পরে। বড় কোডে “এই নামে এখন কী আছে?” প্রশ্নের উত্তর খুঁজতে সময় যায়। আর VM-কে প্রতিবার type দেখতে হয়, তাই গতিও কম।

ভুল ধরা দেরিতে

একটি বিষয় পরিষ্কার রাখুন: dynamic মানে “দুর্বল” নয়। Python-এ "250" + 50 লিখলে সে চুপচাপ কিছু একটা বানিয়ে নেয় না — সোজা TypeError দেয়। JavaScript-এ একই কাজে "25050" পাওয়া যায়, কারণ সেখানে ভাষা নিজেই type বদলে নেয়। এজন্য Python-কে বলা হয় dynamic কিন্তু strong typing-এর ভাষা: type দেরিতে জানা যায়, কিন্তু নিজে থেকে মেশানো হয় না।

C/C++ জানলে

C-তে int x = 5; মানে x নামের একটি নির্দিষ্ট মাপের বাক্স, যার type সারাজীবন int; x = 3.9; লিখলে মান কেটে 3 হয়ে যায়, আর C নিজে থেকে type বদলায় (implicit conversion). Python-এ নামের কোনো type নেই, x = 3.9 নামটিকে একটি নতুন float object-এ সরিয়ে দেয়। আরেকটি বড় পার্থক্য: Python-এর int-এ overflow নেই। C-র দিকটা আবার দেখতে C কোর্সের ভ্যারিয়েবল ও টাইপ পর্ব পড়ুন।

Type hint — নিজের জন্য আর টুলের জন্য নোট

Python 3.5 থেকে আপনি চাইলে নামের পাশে প্রত্যাশিত type লিখে রাখতে পারেন (ভ্যারিয়েবলের জন্য এই লেখার ধরন এসেছে 3.6-এ):

age: int = 20
gpa: float = 4.75
guardian_phone: str | None = None   # str অথবা None (Python 3.10 থেকে এই লেখা)

এগুলোকে বলে type hint. খুব গুরুত্বপূর্ণ কথা: Python চলার সময় type hint যাচাই করে না। age: int = 20 লেখার পর age = "বিশ" করলে প্রোগ্রাম কোনো অভিযোগ ছাড়াই চলবে। তাহলে লাভ কী? মানুষ পড়ে বোঝে কী আশা করা হচ্ছে; VS Code-এর মতো editor ভুল জায়গায় দাগ দেয়; আর mypy-র মতো টুল চালানোর আগেই পুরো কোড পরীক্ষা করে ভুল ধরে। অর্থাৎ dynamic typing-এর স্বাধীনতা রেখেই static typing-এর অনেক সুবিধা নেওয়া যায়। পুরো আলোচনা পর্ব ৩৭-এ।

ধ্রুবক — শুধু প্রথা, তালা নয়

একটি দোকানের হিসাব প্রোগ্রামে VAT-এর হার ১৫%. এই মান প্রোগ্রামের কোথাও বদলানো উচিত নয়। অনেক ভাষায় একে const দিয়ে তালা দেওয়া যায়। Python-এ সত্যিকারের constant নেই — আছে একটি প্রথা: নাম সব বড় হাতে লিখলে সবাই বুঝবে “এটা বদলাবেন না”।

VAT_RATE = 0.15
MAX_LOGIN_TRY = 3
SHOP_NAME = "মায়ের দোয়া স্টোর"

Python আপনাকে VAT_RATE = 0.5 লিখতে বাধা দেবে না। তবে ruff বা mypy-র মতো টুল, আর কোড রিভিউয়ের সহকর্মী — সবাই এটি ধরবে। (চাইলে typing.Final দিয়ে type checker-কে বলে রাখা যায় যে নামটি আর বদলাবে না; সেটাও চলার সময় আটকায় না।) সুবিধা দুটি: সংখ্যার একটি অর্থবহ নাম হলো (0.15 দেখে কেউ বোঝে না এটা VAT), আর হার বদলালে শুধু এক জায়গায় বদলাতে হবে।

পরীক্ষা ও ইন্টারভিউতে
  • Python কি statically typed? — না, dynamically typed: type থাকে object-এর সাথে, চলার সময় জানা যায়; তবে strongly typed, তাই "5" + 5 হলো TypeError.
  • এক লাইনে দুটি ভ্যারিয়েবল swap করুন। — a, b = b, a; ডান দিকের tuple আগে তৈরি হয়, তারপর unpacking.
  • True + True-এর মান কত, কেন? — 2, কারণ bool হলো int-এর subclass, True মানে 1.
  • int("12.5") কী দেয়? — ValueError; আগে float(), তারপর int() করলে 12 (কেটে ফেলে, রাউন্ড নয়)।
  • Falsy মান কোনগুলো? — 0, 0.0, "", খালি list/tuple/dict/set, None, False; "0" আর "False" কিন্তু truthy.

৮ · AI-এর যুগে — নাম আর type নিয়ে AI-এর সাধারণ ভুল

AI কোড লেখায় খুব দ্রুত, আর তার কোড সাধারণত চলেও। কিন্তু “চলে” আর “ভালো” এক নয়। নাম আর type-এর বেলায় AI-এর লেখা কোডে তিনটি সমস্যা বারবার দেখা যায়। Dynamic typing-এর ভাষায় এগুলো চুপচাপ থাকে — compiler নেই যে আগে ধরবে।

AI-এর কোডে যা দেখবেনসমস্যাঠিক করার উপায়
data, temp, x1, res, valঅস্পষ্ট নাম — দুই সপ্তাহ পরে কেউ বুঝবে না কোনটায় কীভেতরে কী আছে তা বলা নাম: monthly_sales, vat_amount
list = [...], str = input(), sum = 0, id = 5Built-in ফাংশন ঢেকে যায়; পরে list("abc") লিখলে TypeError: 'list' object is not callablenames, user_text, total, student_id
একই নামে কখনো সংখ্যা, কখনো লেখাকোন লাইনে কোন type — অনুমান করতে হয়; ভুল type দেরিতে ধরা পড়েএক নাম, এক অর্থ, এক type; দরকারে আলাদা নাম (amount_text, amount)
input()-এর ফল সরাসরি হিসাবেTypeError, বা +-এ জোড়া লাগানো লেখাসীমানাতেই int()/float() দিয়ে বদলানো

দ্বিতীয় সারির সমস্যাটির নাম shadowing — আপনার নিজের নাম Python-এর একটি built-in নামকে ঢেকে দেয়। list, str, sum, max, input, type, id — এগুলো keyword নয়, তাই Python বাধা দেয় না। কিন্তু একবার ঢাকলে সেই ফাইলে আসল ফাংশনটি আর পাওয়া যায় না। REPL-এ ভুলে print = 5 লিখে ফেললে পরের print("হ্যালো")-ও ভেঙে যাবে; del print লিখলে আবার আসলটি ফিরে আসে।

AI-কে কোড লিখতে বলার সময়ই নাম আর type-এর শর্ত দিয়ে দিন। নিচের কাঠামোটি কপি করে ব্যবহার করুন:

একটি Python 3.12+ প্রোগ্রাম লেখো, যা একটি দোকানের বিক্রির পরিমাণ ব্যবহারকারীর কাছ থেকে নিয়ে ১৫% VAT যোগ করে মোট দেখায়। শর্ত: ১. সব ভ্যারিয়েবলের নাম PEP 8 মেনে snake_case, অর্থবহ ইংরেজি শব্দে। data, temp, x, res, val জাতীয় নাম চলবে না। ২. list, str, sum, max, input, type, id-এর মতো কোনো built-in নাম ভ্যারিয়েবল হিসেবে ব্যবহার করবে না। ৩. একটি নামে সবসময় একই type-এর মান রাখবে। input() থেকে আসা লেখা আলাদা নামে রেখে সঙ্গে সঙ্গে সংখ্যায় বদলাও। ৪. VAT-এর হার একটি UPPER_CASE ধ্রুবক হবে। ৫. প্রতিটি ভ্যারিয়েবলের পাশে type hint দাও। ৬. শেষে একটি তালিকা দাও: প্রতিটি নাম, তার type, আর এক লাইনে সে কী রাখে।

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

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

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

প্র ০১Python কেন dynamic typing বেছে নিল? এই সিদ্ধান্তের লাভ-ক্ষতি কী, আর আজকের বড় সফটওয়্যারে এর প্রভাব কেমন?

সিদ্ধান্তটি বুঝতে Python-এর জন্মের প্রেক্ষাপট দেখতে হবে। ১৯৮০-এর দশকে Guido van Rossum নেদারল্যান্ডসের CWI গবেষণাগারে ABC নামের একটি শিক্ষামূলক ভাষার দলে কাজ করেছিলেন। ABC-র লক্ষ্য ছিল প্রোগ্রামিংকে সাধারণ মানুষের জন্য সহজ করা — ভ্যারিয়েবলের type ঘোষণা করতে হতো না, পড়তে সহজ syntax ছিল। এরপর Guido CWI-র Amoeba নামের একটি distributed operating system প্রকল্পে কাজ করতে গিয়ে এমন একটি ভাষার অভাব বোধ করেন, যা shell script-এর চেয়ে শক্তিশালী কিন্তু C-র চেয়ে লিখতে অনেক সহজ। Python সেই ফাঁক পূরণের জন্য তৈরি — ABC-র সরলতা নিয়ে, কিন্তু বাস্তব কাজের জন্য।

এমন “আঠার ভাষা” (glue language)-র জন্য dynamic typing ছিল স্বাভাবিক বাছাই:

  • কম অনুষ্ঠান: ঘোষণা ছাড়াই নাম তৈরি, REPL-এ এক লাইন লিখে সঙ্গে সঙ্গে ফল দেখা। শেখা আর পরীক্ষা দুটোই দ্রুত।
  • নমনীয়তা: একটি ফাংশন যেকোনো type-এর মানে কাজ করতে পারে, যতক্ষণ মানটি প্রয়োজনীয় কাজগুলো পারে। “সবকিছুই object” নকশার সাথে এটি খাপ খায়।
  • সরল interpreter: Compile-এর সময় type-এর জটিল বিশ্লেষণ লাগে না।

দাম কী? প্রথমত, type-এর ভুল ধরা পড়ে দেরিতে — শুধু সেই লাইন চললে। হাজার লাইনের কোডে কোনো দুর্লভ শাখায় লুকানো TypeError মাসের পর মাস টিকে থাকতে পারে। দ্বিতীয়ত, বড় দলে কোড পড়া কঠিন হয় — “এই ফাংশন কী ফেরত দেয়?” জানতে ভেতরটা পড়তে হয়। তৃতীয়ত, গতি: VM-কে প্রতিটি কাজে type দেখতে হয় (পর্ব ০৫)।

বাস্তবে এর ফল দেখা গেছে বড় কোম্পানিগুলোতে। Dropbox-এর মতো প্রতিষ্ঠানে কয়েক লাখ লাইনের Python কোড বাড়ার সাথে সাথে এই সমস্যাগুলো এত বড় হয় যে তারা mypy-র মতো static type checker-এ বিনিয়োগ করে। ফলাফল এক মাঝামাঝি পথ: ছোট স্ক্রিপ্টে পুরো স্বাধীনতা, বড় প্রকল্পে type hint আর checker. Python ভাষা নিজে dynamic-ই রইল, কিন্তু তার চারপাশের টুল static যাচাইয়ের সুবিধা দিল।

মূল উপলব্ধি: Dynamic typing কোনো “ভুল” নয় — এটি প্রোগ্রামারের সময় আর শেখার সহজতাকে আগে রাখার একটি সচেতন trade-off. প্রকল্প বড় হলে সেই trade-off-এর দাম বাড়ে, আর তখন শৃঙ্খলা (স্পষ্ট নাম, type hint, টেস্ট) দিয়েই সেটা মেটাতে হয়।

প্র ০২bool কেন int-এর subclass? True + True == 2 কি নকশার ত্রুটি, নাকি ইচ্ছাকৃত সিদ্ধান্ত?

শুরুর দিকের Python-এ কোনো আলাদা bool type-ই ছিল না। C-র মতোই সত্য-মিথ্যা বোঝাতে ব্যবহার হতো 1 আর 0; তুলনা (3 > 2) ফেরত দিত 1. প্রায় এক দশক ধরে হাজার হাজার প্রোগ্রাম এই ধারণায় লেখা হয়েছিল — কেউ return 1 দিয়ে “সফল” বোঝাত, কেউ তুলনার ফল যোগ করে গুনত।

২০০২ সালে Guido van Rossum PEP 285 প্রস্তাব করেন: একটি আলাদা bool type, দুটি মান True আর False. এটি যোগ হয় Python 2.3-এ। মূল প্রশ্ন ছিল — নতুন type কি int থেকে সম্পূর্ণ আলাদা হবে? Guido সিদ্ধান্ত নেন: না, bool হবে int-এর subclass, True == 1 আর False == 0. কারণগুলো ছিল বাস্তববাদী:

  • পুরোনো কোড না ভাঙা: তুলনা আগে 1/0 দিত, এখন True/False দেয়। যেহেতু True == 1, পুরোনো যেকোনো হিসাব একই ফল দেয়।
  • কাজের সুবিধা: sum(marks >= 33 for marks in all_marks) লিখে সরাসরি পাস করা ছাত্রের সংখ্যা পাওয়া যায়। list-এ True/False দিয়ে index করা যায়।
  • পড়ার সুবিধা: print(3 > 2) এখন True দেখায়, 1 নয় — উদ্দেশ্য স্পষ্ট।

PEP-এর আলোচনায় অনেকে আপত্তি করেছিলেন — বিশুদ্ধতাবাদীরা চাইতেন True + True একটি error হোক, যেমন Pascal-এ সত্য-মিথ্যা সংখ্যা নয়। Guido বিশুদ্ধতার চেয়ে ব্যবহারিকতাকে বেছে নেন। এই সিদ্ধান্তের একটি পার্শ্বফল আপনি এই পর্বে দেখেছেন: isinstance(True, int) হলো True. তাই কোনো ফাংশন যদি “শুধু পূর্ণসংখ্যা” চায় আর isinstance(x, int) দিয়ে যাচাই করে, তবে True-ও পার হয়ে যাবে — কিছু বিশেষ ক্ষেত্রে আলাদা করে bool বাদ দিতে হয়।

অন্য ভাষাগুলো ভিন্ন পথে গেছে। Java-তে boolean সংখ্যা নয়, true + true compile-ই হয় না। C-তে _Bool এসেছে ১৯৯৯-এর মানে, আর সেটিও পূর্ণসংখ্যার একটি রূপ। প্রতিটি ভাষা নিজের ইতিহাস আর ব্যবহারকারীর প্রয়োজন অনুযায়ী বেছে নিয়েছে।

মূল উপলব্ধি: ভাষার নকশায় “তাত্ত্বিকভাবে নিখুঁত” আর “লাখো চলমান প্রোগ্রামের সাথে মানানসই” — এ দুয়ের মধ্যে প্রায়ই টানাপোড়েন হয়। Python-এর দর্শন (“practicality beats purity”) এখানে স্পষ্ট।

প্র ০৩Tony Hoare নিজের আবিষ্কার null reference-কে কেন “billion-dollar mistake” বলেছিলেন? Python-এর None কি একই সমস্যা, নাকি আলাদা?

১৯৬৫ সালে ব্রিটিশ কম্পিউটার বিজ্ঞানী Tony Hoare ALGOL W ভাষার নকশা করার সময় “null reference” যোগ করেন — একটি reference, যা কোনো কিছুকেই দেখায় না। এটা বানানো ছিল সহজ, আর “এখনো মান নেই” বোঝাতে সুবিধাজনক। ২০০৯ সালে লন্ডনের এক সম্মেলনে তিনি এর জন্য প্রকাশ্যে দুঃখ প্রকাশ করেন এবং একে বলেন তাঁর “billion-dollar mistake” — কারণ পরের দশকগুলোতে null-এর কারণে অগণিত error, crash আর নিরাপত্তা-ছিদ্র তৈরি হয়েছে, যার মোট ক্ষতি তাঁর অনুমানে শত কোটি ডলারের মতো।

সমস্যাটা আসলে কোথায়? C, C++ বা Java-তে প্রায় যেকোনো reference বা pointer null হতে পারে, অথচ type দেখে তা বোঝা যায় না। String name লেখা থাকলে মনে হয় এখানে একটি লেখা আছে — কিন্তু হতে পারে null. ভুলে null-এর ওপর কাজ করলে C-তে প্রোগ্রাম crash করে বা অনির্ধারিত আচরণ (undefined behavior) দেখায়, Java-তে NullPointerException.

Python-এর None কোথায় আলাদা:

  • None একটি সত্যিকারের object: এর নিজস্ব type আছে (NoneType), পুরো প্রোগ্রামে মাত্র একটি। এটি “কিছুই না”-র ঠিকানা নয়, বরং “কিছুই না”-র প্রতিনিধি একটি জিনিস।
  • ভুল হলে নিরাপদ ব্যর্থতা: None-এর ওপর .upper() ডাকলে মেমরি নষ্ট হয় না, crash হয় না — একটি স্পষ্ট AttributeError আসে, traceback সহ।
  • Type hint-এ স্পষ্টতা: PEP 484-এর পর থেকে Optional[str], আর Python 3.10 থেকে str | None লেখা যায়। mypy-র মতো checker নিশ্চিত করে যে str | None ব্যবহারের আগে আপনি None-এর সম্ভাবনা যাচাই করেছেন।

তবে Python পুরোপুরি মুক্ত নয়। Dynamic typing-এর কারণে যেকোনো নামে যেকোনো সময় None আসতে পারে, আর type hint ছাড়া কোড দেখে তা বোঝার উপায় নেই। 'NoneType' object has no attribute ... — Python-এ সবচেয়ে বেশি দেখা error-গুলোর একটি। নতুন ভাষাগুলো (Rust-এর Option, Kotlin ও Swift-এর nullable type) আরও কঠোর পথে গেছে: type-এ স্পষ্ট লেখা না থাকলে কোনো মান “কিছু না” হতেই পারে না।

মূল উপলব্ধি: “মান নেই” অবস্থাটা বাস্তবে থাকবেই — প্রশ্ন হলো ভাষা তা কতটা স্পষ্ট করে আর ভুল হলে কতটা নিরাপদে ব্যর্থ হয়। Python নিরাপদ ব্যর্থতা দেয়; স্পষ্টতার দায়িত্ব অনেকটা আপনার (type hint আর is None যাচাই)।

প্র ০৪int("১২") কেন কাজ করে? Python-এর int() যেকোনো লিপির অঙ্ক গ্রহণ করার পেছনে Unicode-এর কোন নিয়ম কাজ করে, আর এর ঝুঁকি কী?

Python 3-এ সব str হলো Unicode লেখা। Unicode শুধু প্রতিটি অক্ষরকে একটি নম্বর দেয় না, প্রতিটি অক্ষরের কিছু বৈশিষ্ট্যও লিখে রাখে — তার মধ্যে একটি হলো “general category”. যেসব অক্ষর কোনো লিপির দশভিত্তিক অঙ্ক (০ থেকে ৯), তাদের বিভাগ Nd (Number, decimal digit), আর প্রত্যেকের সাথে লেখা থাকে তার সাংখ্যিক মান। বাংলা ০ থেকে ৯ (U+09E6 থেকে U+09EF) এই বিভাগেরই সদস্য — যেমন আরবি, দেবনাগরী, তামিল, থাই এবং আরও অনেক লিপির অঙ্ক।

Python-এর ডকুমেন্টেশন স্পষ্ট বলে: int() আর float() লেখা থেকে সংখ্যা পড়ার সময় যেকোনো Unicode decimal digit গ্রহণ করে। তাই int("১২"), int("١٢") (আরবি), int("१२") (দেবনাগরী) — সবই 12. এর পেছনে ভাবনাটি হলো বিশ্বের সব মানুষ নিজের লিপিতে সংখ্যা লেখেন; ফর্মে কেউ বাংলায় “৫০০” লিখলে সেটি ভুল নয়।

সূক্ষ্ম পার্থক্যগুলো:

  • "²" (superscript দুই) বা "½"-এর বিভাগ Nd নয় (No — “অন্যান্য সংখ্যা”), তাই int() এদের প্রত্যাখ্যান করে। অথচ "²".isdigit() হলো True! তাই “এটা কি int-এ বদলানো যাবে?” যাচাইয়ে isdigit() নির্ভরযোগ্য নয়; isdecimal() এর চেয়ে কাছাকাছি, আর সবচেয়ে নিরাপদ হলো সরাসরি চেষ্টা করে ValueError ধরা (পর্ব ২৪)।
  • Python কোডের ভেতরে সংখ্যা (literal) লিখতে কিন্তু শুধু ASCII অঙ্ক চলে — x = ১২ একটি SyntaxError. Unicode অঙ্ক গ্রহণ শুধু চলার সময়ের conversion-এ।
  • উল্টো দিকে str() সবসময় ASCII অঙ্ক দেয়।

ঝুঁকি কী? যদি আপনার প্রোগ্রাম ধরে নেয় “যাচাই করা ইনপুট মানে শুধু 0-9”, অথচ int() অন্য লিপিও গ্রহণ করে, তাহলে দুই জায়গায় দুই নিয়ম — এমন অমিল থেকে নিরাপত্তা-সমস্যা হতে পারে। যেমন একটি অংশ লেখাটি সংখ্যা নয় বলে বাদ দিল, অন্য অংশ সেটিকেই সংখ্যা হিসেবে পড়ল। শুধু ASCII চাইলে আলাদা করে যাচাই করতে হবে (যেমন text.isascii() and text.isdecimal())। বাংলাদেশি অ্যাপে উল্টোটা সুবিধা — ব্যবহারকারী বাংলা কি-বোর্ডে নম্বর লিখলেও প্রোগ্রাম বুঝতে পারে।

মূল উপলব্ধি: “সংখ্যা” আর “অঙ্কের চিহ্ন” আলাদা জিনিস। Unicode যুগে লেখা থেকে সংখ্যা পড়া একটি সাংস্কৃতিক প্রশ্নও — আর প্রতিটি ফাংশন ঠিক কোন নিয়ম মানে, তা জানা না থাকলে সূক্ষ্ম bug তৈরি হয়।

প্র ০৫Duck typing কী, আর “gradual typing” (PEP 484) কীভাবে dynamic আর static typing-এর মাঝে সেতু বানাল?

একটি পুরোনো প্রবাদ আছে: “যা হাঁসের মতো হাঁটে আর হাঁসের মতো ডাকে, তাকে হাঁসই ধরে নাও।” Python সম্প্রদায়ে এর নাম duck typing — প্রচলিত মতে শব্দটি জনপ্রিয় হয় ২০০০ সালের দিকে Python-এর newsgroup-এ Alex Martelli-র লেখা থেকে। মূল ভাবনা: কোনো object-এর type কী, তা বড় কথা নয়; আপনার যে কাজগুলো দরকার, সেগুলো সে পারে কি না — সেটাই আসল। একটি ফাংশন যদি শুধু একটার পর একটা উপাদান পড়ে, তবে list, tuple, string, ফাইল — যা-ই দিন, সব চলবে। isinstance দিয়ে type আটকানোর চেয়ে এটাই বেশি “Pythonic”. এর পূর্ণ রূপ পর্ব ৩৩-এ (ABC আর Protocol).

Duck typing নমনীয়, কিন্তু প্রকল্প বড় হলে সমস্যা — কোন ফাংশন কী আশা করে, তা কোথাও লেখা থাকে না। ২০০০-এর দশকে গবেষক Jeremy Siek আর Walid Taha “gradual typing” নামে একটি তত্ত্ব দেন: একই ভাষায় কিছু অংশে type লেখা থাকবে, কিছু অংশে থাকবে না, আর দুই অংশ নিরাপদে মিশবে। ফিনল্যান্ডের Jukka Lehtosalo পিএইচডি গবেষণার সময় mypy শুরু করেন — Python-এর জন্য একটি ঐচ্ছিক static type checker.

২০১৪ সালে Guido van Rossum, Jukka Lehtosalo আর Łukasz Langa লেখেন PEP 484, যা Python 3.5 (২০১৫)-এ গৃহীত হয়। এর মূল নীতিগুলো:

  • ঐচ্ছিক: type hint না লিখলেও Python আগের মতোই চলে। পুরোনো কোড বদলাতে হয় না।
  • চলার সময় যাচাই নেই: Interpreter hint উপেক্ষা করে; যাচাইয়ের কাজ আলাদা টুলের (mypy, pyright).
  • ধাপে ধাপে: একটি বড় কোডবেসে একটি একটি ফাইল করে hint যোগ করা যায় — “gradual” নামের অর্থ এটাই।

পরে PEP 526 (Python 3.6) ভ্যারিয়েবলের hint (age: int = 20) আনে, PEP 544 (3.8) আনে Protocol — অর্থাৎ duck typing-কেই static যাচাইয়ের ভাষায় প্রকাশের উপায়। আজ বড় Python প্রকল্পের অনেকগুলোতেই type hint নিয়মিত ব্যবহৃত হয়, আর AI-চালিত editor-ও এই hint থেকে ভালো পরামর্শ দেয়।

মূল উপলব্ধি: Python “dynamic না static” — এই দুইয়ের একটি বেছে নেয়নি; বরং বেছে নেওয়ার স্বাধীনতা আপনাকে দিয়েছে। ছোট স্ক্রিপ্টে duck typing-এর স্বাচ্ছন্দ্য, বড় সিস্টেমে type hint-এর নিরাপত্তা — একই ভাষায়।

অনুশীলন

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

  1. লেবেল-ছবি এঁকে অনুমান: কাগজে নাম আর object এঁকে বলুন শেষে কী ছাপা হবে: a = 3, b = a, a = a + 1, a, b = b, a, print(a, b). তারপর student_record.py-তে লাইনগুলো বসিয়ে মিলিয়ে নিন।

    ছাপা হবে 3 4. ধাপগুলো: a → 3; b → একই 3; a + 1 নতুন object 4 বানায়, a সেখানে সরে — b 3-এই থাকে; swap-এ ডান দিক আগে হিসাব হয়ে (3, 4), তাই a → 3, b → 4. মূল শিক্ষা: b = a একটি “সংযোগ” তৈরি করে না, যা পরে a বদলালে b-কেও বদলাবে — int immutable, তাই a-র নতুন মান মানে নতুন object.

  2. নাম সংশোধন: নিচের নামগুলোর কোনটি SyntaxError দেবে, আর কোনটি চলবে কিন্তু খারাপ? প্রতিটির জন্য একটি ভালো নাম প্রস্তাব করুন: 1st_name, totalAmount, list, bill-amount, x (মাসিক বেতন রাখতে), for, is_paid.

    SyntaxError: 1st_name (সংখ্যা দিয়ে শুরু) → first_name; bill-amount (মাঝে বিয়োগ চিহ্ন) → bill_amount; for (keyword) → প্রসঙ্গ অনুযায়ী, যেমন recipient.

    চলবে কিন্তু খারাপ: totalAmount — camelCase, PEP 8 অনুযায়ী total_amount; list — built-in ঢেকে দেয় (shadowing), বরং items বা names; x — কিছু বলে না, বরং monthly_salary. ঠিক আছে: is_paid — snake_case, আর প্রশ্নের মতো নাম বলে বোঝা যায় এটি একটি bool.

  3. Conversion অনুমান: চালানোর আগে লিখুন প্রতিটির ফল কী, নাকি ValueError: int("৭"), int("7.0"), float("1e3"), int(" -5 "), int("²"), bool("False"), int(True). তারপর REPL-এ একটি একটি করে যাচাই করুন।

    int("৭") → 7 (বাংলা অঙ্ক Unicode decimal digit); int("7.0") → ValueError (দশমিক বিন্দু; int(float("7.0")) লাগবে); float("1e3") → 1000.0 (বৈজ্ঞানিক লেখা চলে); int(" -5 ") → -5 (ফাঁকা জায়গা আর চিহ্ন চলে); int("²") → ValueError (superscript, decimal digit নয়); bool("False") → True (খালি নয়); int(True) → 1 (bool আসলে int). যেগুলোতে ভুল করেছেন, সেগুলোই আপনার আসল শেখার জায়গা।

  4. AI অডিট: যেকোনো AI-কে কোনো শর্ত ছাড়া বলুন: “Python-এ একটি প্রোগ্রাম লেখো যা কয়েকজন ছাত্রের নম্বর নিয়ে গড় বের করে।” পাওয়া কোডটি এই পর্বের চেকলিস্ট ধরে যাচাই করুন। তারপর পর্বের prompt-টির মতো শর্ত দিয়ে আবার চান। দুই উত্তরের পার্থক্য লিখুন।

    শর্ত ছাড়া উত্তরে প্রায়ই দেখবেন data, nums, res জাতীয় নাম, কখনো sum = 0 বা list = [] (built-in ঢেকে দেওয়া — পরে sum(...) ডাকলে TypeError), আর input()-এর ফল int() ছাড়াই ব্যবহার। শর্ত দিলে নামগুলো সাধারণত ভালো হয় — marks, average_mark — আর type hint আসে। তবু নিজে চেকলিস্ট মিলিয়ে দেখুন; AI শর্ত মানল বলে দাবি করলেও সব জায়গায় মানে না। আর একবার খালি ইনপুট দিয়ে চালান — দুটি সংস্করণের কোনটি ভালোভাবে ব্যর্থ হয়?

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

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

ক্রমPython-এ a, b = b, a চলার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ➡️ ডান দিকে প্রথমে b-এর মান আনা
  2. ➡️ তারপর a-এর মান আনা
  3. 📦 দুটি মান দিয়ে একটি tuple বানানো (packing)
  4. 🏷️ tuple-এর প্রথম মানে a নামটি লাগানো
  5. 🏷️ tuple-এর দ্বিতীয় মানে b নামটি লাগানো

Python-এ নিচের কোনটি বৈধ ভ্যারিয়েবলের নাম?

  • 2nd_roll
  • my-name
  • student_name
  • class

x = 5, তারপর x = "পাঁচ" চালানোর পর type(x) কী দেবে?

  • <class 'int'>
  • <class 'str'>
  • TypeError
  • <class 'int|str'>

Python-এ True + True-এর মান কী?

  • TypeError
  • True
  • 'TrueTrue'
  • 2

Python-এ int("12.5") চালালে কী ঘটে?

  • ValueError
  • 13
  • 12
  • TypeError

নিচের কোনটির bool() মান False?

  • bool("0")
  • bool(" ")
  • bool([])
  • bool("False")

Python-এ age: int = 20 লেখার পর age = "বিশ" চালালে কী হয়?

  • TypeError আসে, কারণ age-কে int ঘোষণা করা হয়েছে
  • প্রোগ্রাম ঠিকঠাক চলে; mypy-র মতো টুল চালালে তবেই ভুলটি ধরা পড়ে
  • "বিশ" স্বয়ংক্রিয়ভাবে 20-এ বদলে যায়
  • SyntaxError আসে

শব্দকোষ

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

মনে রাখুন

  1. ভ্যারিয়েবল হলো একটি মানের গায়ে লাগানো নাম; = মানে assignment — আগে ডান দিক হিসাব, তারপর বাম দিকের নাম লাগানো। সমান কিনা দেখতে ==.
  2. নাম: অক্ষর বা _ দিয়ে শুরু, keyword নয়, case-sensitive; PEP 8 অনুযায়ী snake_case, ধ্রুবক UPPER_CASE; built-in নাম ঢাকবেন না।
  3. Type থাকে object-এর গায়ে, নামের নয় — তাই একই নাম পরে অন্য type দেখাতে পারে (dynamic typing). a, b = b, a swap করে; x = y = [] একটিই list.
  4. মৌলিক type: int, float, bool (int-এর subclass), str, NoneType; জানতে type(), যাচাই করতে isinstance(); None মানে “এখনো মান নেই”, যাচাই is None.
  5. input() সবসময় str; int()/float() দিয়ে বদলান, ভুল লেখায় ValueError; বাংলা অঙ্কও চলে। খালি বা শূন্য জিনিস falsy, "0" কিন্তু truthy. Type hint চলার সময় যাচাই হয় না।

পরের পর্বে: সংখ্যার জগতে গভীরে — int কীভাবে অসীম বড় হতে পারে, 0.1 + 0.2 কেন 0.3 নয়, // আর %-এর রহস্য, আর টাকার হিসাবে কেন Decimal.

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

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