আনপ্যাকিং বলতে ইটারেশনের মাধ্যমে একটি কালেকশনের এলিমেন্ট, যেমন list, tuple বা dict, বের করে আনার কাজকে বোঝায়।
এরপর আনপ্যাক করা মানগুলো একই স্টেটমেন্টের মধ্যে ভ্যারিয়েবলে অ্যাসাইন করা যায়।
এই আচরণের খুব কমন একটি উদাহরণ হলো for item in list, যেখানে ইটারেশন চলাকালীন item পর্যায়ক্রমে প্রতিটি list এলিমেন্টের মান গ্রহণ করে।
মাল্টিপল অ্যাসাইনমেন্ট হলো একই স্টেটমেন্টে একাধিক ভ্যারিয়েবলে আনপ্যাক করা মান অ্যাসাইন করার ক্ষমতা।
এতে কোড আরও সংক্ষিপ্ত ও পাঠযোগ্য হয়, আর এটি করা হয় যে ভ্যারিয়েবলগুলো অ্যাসাইন করতে হবে সেগুলো কমা দিয়ে আলাদা করে, যেমন first, second, third = (1,2,3) অথবা for index, item in enumerate(iterable)।
বিশেষ অপারেটর * ও ** প্রায়ই আনপ্যাকিংয়ের ক্ষেত্রে ব্যবহৃত হয়।
* ব্যবহার করে একাধিক list/tuple থেকে প্রতিটিকে আনপ্যাক করে একটি নতুন কমন list/tuple বানানো যায়।
আর ** ব্যবহার করে একাধিক ডিকশনারি থেকে প্রতিটিকে আনপ্যাক করে একটি নতুন কমন dict বানানো যায়।
যখন * অপারেটর কোনো কালেকশন ছাড়া ব্যবহৃত হয়, তখন এটি একগুচ্ছ মানকে একটি list-এ প্যাক করে।
মাল্টিপল অ্যাসাইনমেন্টে এটি প্রায়ই ব্যবহৃত হয়, যাতে যেসব "বাড়তি" এলিমেন্টের আলাদা অ্যাসাইনমেন্ট নেই সেগুলোকে একটি ভ্যারিয়েবলে একত্র করা যায়।
Python-এ এমন ফাংশন ব্যবহার বা ডিফাইন করার সময়ও এই আনপ্যাকিং/প্যাকিং আচরণ কাজে লাগানো কমন, যেগুলো ইচ্ছামতো সংখ্যক পজিশনাল বা কিওয়ার্ড আর্গুমেন্ট নেয়।
আপনি প্রায়ই দেখবেন এই "বিশেষ" প্যারামিটারগুলো def some_function(*args, **kwargs) হিসেবে ডিফাইন করা হয়েছে এবং "বিশেষ" আর্গুমেন্টগুলো some_function(*some_tuple, **some_dict) হিসেবে ব্যবহৃত হয়েছে।
*<variable_name> ও **<variable_name> মূল * ও ** থেকে আলাদা জিনিস, এদের গুলিয়ে ফেলা উচিত নয়। * ও ** যথাক্রমে গুণ ও সূচকীকরণের জন্য ব্যবহৃত হয়, আর *<variable_name> ও **<variable_name> ব্যবহৃত হয় প্যাকিং ও আনপ্যাকিং অপারেটর হিসেবে।
মাল্টিপল অ্যাসাইনমেন্টে অ্যাসাইনমেন্ট অপারেটর (=) এর বাম দিকে ভ্যারিয়েবলের সংখ্যা আর ডান দিকে মানের সংখ্যা অবশ্যই মিলতে হবে।
মানগুলো আলাদা করতে কমা , ব্যবহার করুন:
>>> a, b = 1, 2
>>> a
1
প্রদত্ত মানের জন্য মাল্টিপল অ্যাসাইনমেন্টে যদি ভুল সংখ্যক ভ্যারিয়েবল থাকে, তাহলে একটি ValueError থ্রো হবে:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
মাল্টিপল অ্যাসাইনমেন্ট একটি ডেটা টাইপেই সীমাবদ্ধ নয়:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
মাল্টিপল অ্যাসাইনমেন্ট দিয়ে lists-এর ভেতরের এলিমেন্টগুলো অদলবদল করা যায়।
সর্টিং অ্যালগরিদম-এ এটি বেশ কমন একটি অভ্যাস।
উদাহরণস্বরূপ:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
যেহেতু tuples অপরিবর্তনীয়, তাই কোনো tuple-এর এলিমেন্ট অদলবদল করা যায় না।
নিচের উদাহরণগুলোতে lists ব্যবহার করা হয়েছে, তবে একই ধারণা tuples-এর ক্ষেত্রেও প্রযোজ্য।
Python-এ একটি list/tuple/dict-এর এলিমেন্ট আলাদা আলাদা ভ্যারিয়েবলে আনপ্যাক করা সম্ভব।
যেহেতু lists/tuples-এর ভেতরে মানগুলো একটি নির্দিষ্ট ক্রমে থাকে, তাই সেগুলোও একই ক্রমে ভ্যারিয়েবলে আনপ্যাক হয়:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
যদি এমন মান থাকে যেগুলোর প্রয়োজন নেই, তাহলে সেগুলো চিহ্নিত করতে _ ব্যবহার করতে পারেন:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
একটি list বা tuple-এর ভেতরে থাকা list/tuple থেকে মান আনপ্যাক ও অ্যাসাইন করা (যা নেস্টেড লিস্ট/টুপল নামেও পরিচিত) অনেকটা শ্যালো আনপ্যাকিংয়ের মতোই কাজ করে, তবে মানটির প্রসঙ্গ বা অবস্থান স্পষ্ট করতে প্রায়ই কিছু কোয়ালিফায়ার লাগে:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
একটি নেস্টেড list/tuple-এর শুধু একটি অংশও ডিপ আনপ্যাক করা যায়:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
যদি আনপ্যাকিংয়ে ভ্যারিয়েবলগুলোর অবস্থান ভুল হয় এবং/অথবা মানের সংখ্যা ভুল হয়, তাহলে একটি ValueError পাবেন:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
* দিয়ে একটি list/tuple আনপ্যাক করাএকটি list/tuple আনপ্যাক করার সময় "বাড়তি" মানগুলো ধরতে * অপারেটর ব্যবহার করতে পারেন।
list/tuple স্লাইস করার চেয়ে (যা কিছু ক্ষেত্রে কম পাঠযোগ্য) এটি বেশি স্পষ্ট।
উদাহরণস্বরূপ, প্রথম এলিমেন্টটি বের করে বাকি মানগুলো প্রথম এলিমেন্ট ছাড়া একটি নতুন list-এ প্যাক করা যায়:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
list-এর শুরু ও শেষের মানগুলো বের করে মাঝের সব মান একসাথে গুছিয়েও রাখা যায়:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
ডিপ আনপ্যাকিংয়েও * ব্যবহার করা যায়:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
ডিকশনারি আনপ্যাক করা list/tuple আনপ্যাক করার চেয়ে একটু আলাদা।
ডিকশনারির ওপর ইটারেশন ডিফল্টভাবে কী (key)-গুলোর ওপর হয়।
তাই dict আনপ্যাক করার সময় আপনি শুধু কী (key) আনপ্যাক করতে পারেন, মান নয়:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
যদি মানগুলো আনপ্যাক করতে চান, তাহলে <dict>.values() মেথড ব্যবহার করতে পারেন:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
যদি কী (key) আর মান দুটোই দরকার হয়, তাহলে <dict>.items() মেথডটি ব্যবহার করুন।
<dict>.items() কী (key) আর মান-এর জোড়া ধারণ করা একটি ইটারেবল ভিউ তৈরি করে।
এগুলো একটি tuple-এ আনপ্যাক করা যায়:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
প্যাকিং হলো একাধিক মানকে একটি list-এ একত্র করে একটি ভ্যারিয়েবলে অ্যাসাইন করার ক্ষমতা।
যখন আপনি মানগুলো আনপ্যাক করে পরিবর্তন করতে চান, আর তারপর ফলাফলগুলো আবার একটি ভ্যারিয়েবলে প্যাক করতে চান, তখন এটি কাজে আসে।
এটি দিয়ে ২ বা তার বেশি lists/tuples/dicts-এর ওপর মার্জ করাও সম্ভব।
* দিয়ে একটি list/tuple প্যাক করা* অপারেটর ব্যবহার করে একটি list/tuple প্যাক করা যায়।
এটি সব মানকে একটি list/tuple-এ প্যাক করে।
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
* ও **-এর ব্যবহার সম্পর্কে আরও বিস্তারিত জানতে PEP 3132 ও PEP 448 দেখুন।
** দিয়ে একটি ডিকশনারি প্যাক করা** অপারেটর ব্যবহার করে একটি ডিকশনারি প্যাক করা হয়।
এটি একটি ডিকশনারির সব কী (key)-মান জোড়া আরেকটি ডিকশনারিতে প্যাক করে, অথবা দুটি ডিকশনারি একসাথে মেলায়।
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
* ও **-এর ব্যবহারযখন এমন একটি ফাংশন তৈরি করেন যা ইচ্ছামতো সংখ্যক আর্গুমেন্ট নেয়, তখন ফাংশনের ডেফিনিশনে *args অথবা **kwargs ব্যবহার করতে পারেন।
*args ব্যবহৃত হয় ইচ্ছামতো সংখ্যক পজিশনাল (নন-কিওয়ার্ডেড) আর্গুমেন্ট প্যাক করতে, আর
**kwargs ব্যবহৃত হয় ইচ্ছামতো সংখ্যক কিওয়ার্ড আর্গুমেন্ট প্যাক করতে।
*args-এর ব্যবহার:
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
**kwargs-এর ব্যবহার:
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args আর **kwargs একসাথে মিলিয়েও ব্যবহার করা যায়:
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
নির্দিষ্ট পজিশনাল আর্গুমেন্টের সুবিধার জন্য *args-এর আগে প্যারামিটারও লিখতে পারেন।
তখন আলাদা আলাদা কিওয়ার্ড আর্গুমেন্ট **kwargs-এর আগে থাকতে হবে।
আর্গুমেন্টগুলোকে এভাবে সাজাতে হবে:
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
এই ক্রম অনুসরণ না করলে এরর পাবেন।
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
ভুল ক্রমে আর্গুমেন্ট লিখলে এরর হবে:
>>> def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
ফাংশন কলে একটি list/tuple আর্গুমেন্ট আনপ্যাক করতে * ব্যবহার করতে পারেন।
যে ফাংশনগুলো iterable নেয় না, তাদের জন্য এটি খুবই কাজের:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
zip() বিল্ট-ইনটি * আনপ্যাকিংয়ের সাথে ব্যবহার করাও একটি কমন ক্ষেত্র।
zip() ফাংশন একাধিক ইটারেবল নেয় আর প্রতিটি iterable থেকে মানগুলো একসাথে গুছিয়ে tuples-এর একটি list রিটার্ন করে:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]