অধ্যায় 14

টাপল আর আনপ্যাকিং

বদলানো যায় না এমন লিস্ট — কোথায় সেটাই দরকার, আনপ্যাকিং দিয়ে সূচকের বদলে নাম ব্যবহার, এক লাইনে অদলবদল, আর (5) বনাম (5,)-এর ফাঁদ।

30 মিনিটPython 3.12
  1. 1সমস্যা
  2. 2বোঝা
  3. 3উদাহরণ
  4. 4অনুমান
  5. 5নিজে করা
  6. 6কঠিন করা

যে সমস্যাটা আমরা সমাধান করছি

তিন দিনের আবহাওয়ার হিসাব রাখতে হবে — প্রতিদিনের দিন, তাপমাত্রা আর আর্দ্রতা।

লিস্ট দিয়ে করা যায়: ["monday", 31.5, 78]। কাজ চলবে, কিন্তু দুটো জিনিস খচখচ করে।

প্রথমত, এই লিস্টটা বদলানো যায়। সোমবারের রেকর্ডে কেউ ভুল করে append করে বসলে পাইথন কিছুই বলবে না — অথচ একটা রেকর্ডে চারটি জিনিস থাকার কথাই নয়। যে জিনিসটা বদলানোর কথা নয়, তাকে বদলানো যায় এমন পাত্রে রাখা একটা নীরব ঝুঁকি।

দ্বিতীয়ত, ভেতর থেকে তিনটি মান বের করতে হলে লিখতে হয় record[0], record[1], record[2] — সংখ্যা দিয়ে, নাম দিয়ে নয়। ছয় মাস পর record[1] পড়ে কেউ বুঝবে না ওটা তাপমাত্রা না আর্দ্রতা।

টাপল দুটোরই উত্তর: এটি একটি লিস্ট, কিন্তু বদলানো যায় না — আর তার সাথে আসে একটি সুবিধা, যার নাম আনপ্যাকিং, যা সূচকের বদলে নাম ব্যবহার করতে দেয়।

এই অধ্যায় শেষে আপনি পারবেন

  • টাপল বানাতে, আর বলতে কোথায় লিস্ট নয় টাপল লাগে
  • আনপ্যাকিং দিয়ে কয়েকটি মান একসাথে কয়েকটি নামে বসাতে
  • এক লাইনে দুটি ভেরিয়েবলের মান অদলবদল করতে
  • (5) আর (5,)-এর পার্থক্য ধরতে
  • টাপলের উপর append করতে গেলে কেন AttributeError আসে, বলতে

আগে যা জানা লাগবে: লিস্ট সাজানো আর তার মেথডগুলো।


টাপল — বদলানো যায় না এমন লিস্ট

বর্গবন্ধনীর বদলে সাধারণ বন্ধনী:

python
point = (3, 7)

print(point)
print(point[0])
print(len(point))
text
(3, 7)
3
2

পড়া, সূচক, len, স্লাইস, in, for দিয়ে ঘোরা — লিস্টে যা যা করতেন সবই চলে। পার্থক্য একটাই, আর সেটাই সব:

python
point = (3, 7)
point[0] = 5
text
TypeError: 'tuple' object does not support item assignment

বার্তাটা চেনা লাগছে? ষষ্ঠ অধ্যায়ে লেখার ভেতরের অক্ষর বদলাতে গিয়ে ঠিক এই বার্তাটাই এসেছিল, শুধু 'str' লেখা ছিল। পাইথনে অপরিবর্তনীয় জিনিসগুলো একই ভাষায় অভিযোগ করে।

বদলানোর মেথডগুলোও নেই:

python
point = (3, 7)
point.append(9)
text
AttributeError: 'tuple' object has no attribute 'append'

AttributeError মানে «এই ধরনের জিনিসের এমন কোনো মেথড নেই»। টাপল বদলানো যায় না, তাই বদলানোর মেথডগুলো তার নেই — TypeError নয়, কারণ সমস্যাটা কাজটার নয়, জিনিসটার।

কেন এটা সুবিধা, বাধা নয়

প্রথমে মনে হয় টাপল হলো কম ক্ষমতার লিস্ট। উল্টো করে ভাবুন: যে জিনিসটা বদলানোর কথা নয়, তাকে বদলানো যায় না এমন পাত্রে রাখলে ভুলটা ঘটার আগেই আটকে যায়।

একটি স্থানাঙ্ক (3, 7)-এ তৃতীয় সংখ্যা যোগ করার মানে হয় না। একটি তারিখ (2026, 9, 16)-এ চতুর্থ কিছু থাকার কথা নয়। এই জিনিসগুলো টাপলে রাখলে দুর্ঘটনাটা ত্রুটি হয়ে সাথে সাথে ধরা পড়ে, বাগ হয়ে পরে নয়।

নিয়ম হিসেবে: কয়টি জিনিস আর তাদের অর্থ আগে থেকেই ঠিক — টাপল। কয়টি হবে জানা নেই, আর সবগুলো একই জাতের — লিস্ট।

তিন দিনের আবহাওয়া তাই লিস্টের ভেতরে টাপল: কয়টি দিন হবে জানা নেই (লিস্ট), কিন্তু প্রতিটি রেকর্ডে ঠিক তিনটি জিনিস (টাপল)।

আনপ্যাকিং — আসল সুবিধাটা এখানে

python
point = (3, 7)
x, y = point

print(x)
print(y)
text
3
7

এক লাইনে দুটি নাম, দুটি মান। আর এখন point[1] নয়, y — যে নামটা পড়লেই বোঝা যায়।

বাঁ পাশে যতগুলো নাম, ডান পাশে ঠিক ততগুলো মান থাকতে হবে:

python
point = (3, 7)
x, y, z = point
text
ValueError: not enough values to unpack (expected 3, got 2)

বার্তাটা অস্বাভাবিক রকম সহায়ক — কয়টি আশা করা হয়েছিল আর কয়টি পাওয়া গেছে, দুটোই বলে দেয়।

আনপ্যাকিং টাপলেই সীমাবদ্ধ নয়, লিস্টেও চলে। কিন্তু সবচেয়ে বেশি কাজে লাগে টাপলের সাথেই, কারণ টাপলেই সংখ্যাটা আগে থেকে জানা।

অদলবদল — এক লাইনে

অন্য ভাষায় দুটি ভেরিয়েবলের মান বদলাবদলি করতে একটি অস্থায়ী তৃতীয় ভেরিয়েবল লাগে। পাইথনে লাগে না:

python
a = 1
b = 2

a, b = b, a
print(a, b)
text
2 1

লাইনটি কীভাবে কাজ করে সেটা বোঝা জরুরি: পাইথন আগে ডান পাশটা পুরো হিসাব করে, একটি টাপল (2, 1) বানায়, তারপর সেটিকে বাঁ পাশে আনপ্যাক করে। তাই a বদলে গেলেও b-এর পুরোনো মান ততক্ষণে ধরা আছে, আর কিছু হারায় না।

বন্ধনী আসলে বাধ্যতামূলক নয়

python
point = 3, 7
print(point)
print(type(point))
text
(3, 7)
<class 'tuple'>

টাপল তৈরি করে কমা, বন্ধনী নয়। বন্ধনী কেবল পড়ার সুবিধার জন্য। এটা জানা থাকলে a, b = b, a লাইনটা আর জাদু মনে হয় না — ডান পাশে b, a একটি টাপলই।

আর এখান থেকেই একটি ফাঁদ আসে:

python
one = (5)
really = (5,)

print(type(one))
print(type(really))
print(len(really))
text
<class 'int'>
<class 'tuple'>
1

(5) কোনো টাপল নয় — ওটা কেবল বন্ধনীতে মোড়া একটি সংখ্যা, ঠিক যেমন (2 + 3)। একটিমাত্র মানের টাপল বানাতে হলে কমা লাগবেই: (5,)। দেখতে টাইপো লাগে, কিন্তু ওই কমাটাই পুরো পার্থক্য।

লুপে টাপল

python
people = [("rafi", 22), ("ahmed", 35)]

for name, age in people:
    print(f"{name} is {age}")
text
rafi is 22
ahmed is 35

for name, age in people প্রতিটি টাপলকে সরাসরি দুটি নামে আনপ্যাক করে দিচ্ছে। person[0] আর person[1] লেখার দরকার নেই — আর কোডটা পড়লেই বোঝা যায় কোনটা কী।

এই কৌশলটা আপনি আগেই ব্যবহার করেছেন, না জেনে:

python
stock = ["pen", "bag"]

for pair in enumerate(stock):
    print(pair)
text
(0, 'pen')
(1, 'bag')

enumerate আসলে টাপলই দেয়। গত অধ্যায়ে for position, item in enumerate(...) লিখে আমরা সেই টাপলগুলোকে আনপ্যাক করছিলাম — তখন নামই জানতাম না।

এদিক-ওদিক বদলানো

python
as_list = [3, 7]
as_tuple = (3, 7)

as_list.append(9)
print(as_list)
print(as_tuple)
print(list(as_tuple))
print(tuple(as_list))
text
[3, 7, 9]
(3, 7)
[3, 7]
(3, 7, 9)

list() আর tuple() একটি থেকে অন্যটি বানায় — নতুন একটি জিনিস বানায়, পুরোনোটা অক্ষত থাকে। একটি টাপল «বদলাতে» হলে এটাই উপায়: লিস্ট বানান, বদলান, আবার টাপল বানান।


একটা সম্পূর্ণ উদাহরণ

weather.py:

python
# A tuple is the right shape for a record that must not change
readings = [
    ("monday", 31.5, 78),
    ("tuesday", 33.0, 71),
    ("wednesday", 29.5, 85),
]

print("Day        Temp   Humidity")
for day, temp, humidity in readings:
    print(f"{day:<10} {temp:>5}  {humidity:>5}")

hottest = readings[0]
for reading in readings:
    if reading[1] > hottest[1]:
        hottest = reading

day, temp, humidity = hottest
print()
print(f"Hottest was {day} at {temp}")

temps = []
for _, temp, _ in readings:
    temps.append(temp)

print("Temps      :", temps)
print("Average    :", round(sum(temps) / len(temps), 2))
text
Day        Temp   Humidity
monday      31.5     78
tuesday     33.0     71
wednesday   29.5     85

Hottest was tuesday at 33.0
Temps      : [31.5, 33.0, 29.5]
Average    : 31.33

চারটি জিনিস লক্ষ করার মতো।

বাইরে লিস্ট, ভেতরে টাপল। দিনের সংখ্যা বাড়তে পারে, তাই বাইরেরটা লিস্ট। প্রতিটি রেকর্ডে ঠিক তিনটি জিনিস, তাই ভেতরেরগুলো টাপল। এটাই বাস্তব ডেটার সবচেয়ে সাধারণ আকার।

f"{day:<10}" আর f"{temp:>5}" — কলাম সোজা রাখার জন্য। < বাঁয়ে ঘেঁষায়, > ডানে, আর সংখ্যাটা কত ঘর জায়গা নেবে তা বলে। ষষ্ঠ অধ্যায়ের :.2f-এর মতোই, কোলনের পরের অংশটুকু কেবল সাজসজ্জা।

সবচেয়ে গরম দিনটা খুঁজতে reading[1] লিখতে হয়েছে, কারণ তুলনার সময় গোটা টাপলটাই হাতে আছে, আনপ্যাক করা নয়। পাওয়ার পর সাথে সাথেই আনপ্যাক করা হয়েছে — তাই তার পরের লাইনে আর সূচক নেই।

for _, temp, _ in readings — আন্ডারস্কোরটি একটি প্রচলিত রীতি, যার অর্থ «এই মানটা লাগবে না»। পাইথনের কাছে এটি আর দশটা নামের মতোই, কিন্তু পড়ার সময় এটি বলে দেয় কোন অংশটুকু ইচ্ছে করে ফেলে দেওয়া হচ্ছে।


কিছু ভাঙা অবস্থা ও তার সমাধান

TypeError: 'tuple' object does not support item assignment টাপলের ভেতরের একটি মান বদলানোর চেষ্টা। টাপল বদলানো যায় না — হয় শুরুতেই লিস্ট ব্যবহার করুন, নয়তো list(...) দিয়ে বদলে নিয়ে কাজ করুন।

AttributeError: 'tuple' object has no attribute 'append' একই কারণ, অন্য চেহারায়। append, remove, sort — বদলানোর কোনো মেথডই টাপলের নেই। তবে sorted(my_tuple) কাজ করে, কারণ সে নতুন একটি লিস্ট ফেরত দেয়, টাপলটাকে ছোঁয় না।

ValueError: not enough values to unpack (expected 3, got 2) বাঁ পাশে যতগুলো নাম, ডান পাশে ততগুলো মান নেই। print(len(record)) দিয়ে দেখে নিন ভেতরে আসলে কয়টি আছে।

ValueError: too many values to unpack (expected 2) উল্টো সমস্যা — মান বেশি, নাম কম।

একটিমাত্র মানের টাপল বানিয়েছি, কিন্তু সেটা টাপল হয়নি কমা বাদ পড়েছে। (5) একটি সংখ্যা, (5,) একটি টাপল।

টাপলের ভেতরের লিস্টটা বদলে গেছে টাপল তার নিজের গঠন রক্ষা করে — কোন জিনিসগুলো তার ভেতরে আছে, সেটা বদলানো যায় না। কিন্তু সেই জিনিসগুলোর একটি যদি নিজেই বদলানোর যোগ্য হয়, যেমন একটি লিস্ট, তাহলে সেটির ভেতরটা বদলানো যায়। ([1, 2], 3)-এ প্রথম লিস্টটিতে append করা চলে।