পাইথন ডেকোরেটর: পর্ব - ১

পাইথন ডেকোরেটর: পর্ব - ১
২১ জুন, সোমবার, ২০২১

বেসিক পাইথন আমি মোটামুটি জানি। টুকটাক কাজও করেছি। কিন্তু পাইথনের এডভান্সড জিনিসপত্র শিখবো শিখবো করেও শেখা হচ্ছিল না। বেসিক পাইথনের আমি যতটুকু জানি, তার কমবেশি আমার "সহজ বাংলায় পাইথন" আর "সহজ বাংলায় পাইথনে ডেটা স্ট্রাকচার" ইউটিউব ভিডিও সিরিজের ভিডিওগুলোতে আলোচনা করেছি। এই ভিডিও গুলোর একটা সুবিধা হচ্ছে, অনেকেই অনেক সময় কমেন্ট করে নতুন কোনো টপিকের উপর ভিডিও বানানোর অনুরোধ করেন। সেইরকম একটা অনুরোধ ছিল এই পাইথন ডেকোরেটর। কাজেই বুঝতে পারছেন, আমি নিজে শিখে আজকে কিছু লেখার চেষ্টা করছি। কোত্থেকে শিখছি সেটার সম্পর্কেও একটু বলি। আমাদের ডিপার্টমেন্টের জুনিয়র, ফরহাদ নাঈমের কাছ থেকে পাইথনের উপর লুসিয়ানো রামালহো-র লেখা 'ফ্লুয়েন্ট পাইথন' বইটার নাম জেনেছিলাম। ফরহাদ খুবই কাজের একটা ছেলে, ওর টেকনোলজির উপর দখল অনেক ভালো। ওর কথাতেই বইটা একটু ঘাঁটাঘাঁটি করে দেখে আমি মুগ্ধ। খুবই ভালো বই। আজকের পুরো লেখাটাই এই বইয়ের 'Function Decorators and Closures' - চ্যাপ্টারের প্রথম অংশ থেকে নেয়া - নিজের মতো করে লেখার চেষ্টা করছি।    

পাইথন ডেকোরেটর বোঝার জন্য কয়েকটা জিনিস জানতে হবে: সবগুলোই এক এক করে উদাহরণ দিয়ে পরিষ্কার করার চেষ্ঠা করছি: 

  • ১. পাইথন 'ফার্স্ট ক্লাস' ফাঙ্কশন সাপোর্ট করে  - অর্থাৎ, পাইথনে ফাঙ্কশন একটা অবজেক্ট। 

একটা ফাঙ্কশনকে কোনো ভ্যারিয়বলে -এ এসাইন করা, অন্য ফাঙ্কশনে প্যারামিটার হিসাবে পাস্ করা, অন্য ফাঙ্কশন থেকে রিটার্ন করা, কোনো ডেটা স্ট্রাকচার (যেমন, পাইথন লিস্ট, ডিকশনারী, সেট ইত্যাদি) -এ স্টোর করা যায়। নিচের ছবি ১ ও ২ খেয়াল করলে প্রথম দুইটা ব্যাপার পরিষ্কার হবে। 


ছবি ১: একটা ফাঙ্কশনকে অন্য ভেরিএবলে এসাইন করিয়ে, সেটা দিয়ে অন্য নামে কল করা যায় 


ছবি - ২ -, লাইন ১৪ তে  bar() ফাঙ্কশন foo ফাঙ্কশনকে প্যারামিটার হিসাবে নিলো, নিজে 'Inside bar  ...' প্রিন্ট করে তারপর foo ফাঙ্কশন কল করলো, যেটা 'Inside foo  ...' প্রিন্ট করলো।       

ছবি ২: একটা ফাঙ্কশন অন্য ফাঙ্কশনকে প্যারামিটার হিসাবে নিয়ে কল করতে পারে 

  • ২. দুটো ফাঙ্কশন মাথায় রাখতে হবে, প্রথমটা হচ্ছে টার্গেট ফাঙ্কশন - যেটাকে ডেকোরেট (বা সাজানো) হবে, আর দ্বিতীয়টা হচ্ছে ডেকোরেটর ফাঙ্কশন, যেটা কিনা টার্গেট মেথডকে ডেকোরেট করবে বা সাজাবে।

আর এজন্য টার্গেট মেথডের শুরুতে '@' চিহ্ন ব্যবহার করে ডেকোরেটর মেথডের নাম লিখে দিতে হয়। এযেন টার্গেটের কানে দুল পরিয়ে সাজিয়ে দেয়া 😀। আসলে ডেকোরেটর মেথড টার্গেট মেথডকে প্যারামিটার হিসাবে নেয়। ডেকোরেটর মেথড নিজের মধ্যে অন্য কিছু করতে পারে, তারপর পাস্ হওয়া টার্গেট মেথড কল করতে পারে। সব কাজ শেষ হলে, রিটার্ন করতে পারে অরিজিনাল টার্গেট মেথড, অথবা অন্য inner মেথড (এ ব্যাপারে পরে বলছি)।   

ছবি ৩: ডেকোরেটর  মেথড টার্গেট মেথডকে প্যারামিটার হিসাবে নেয় 

  • ৩. ডেকোরেটর ফাঙ্কশন যে মডিউল-এ ডিফাইন করা আছে, সেই মডিউল লোড হওয়ার সময়ই, অর্থাৎ ইম্পোর্ট করার সময় (import time) ডেকোরেটর ফাঙ্কশন আপনা-আপনি 'কল' হয়ে যায়।
ছবি -৩ খেয়াল করলে দেখবেন, কেউ কিন্তু টার্গেট কিংবা ডেকোরেটর কোনো মেথডকেই কল করে নাই, কোনো কিছু প্রিন্ট করে নাই। কিন্তু তারপরও আউটপুট দেখলে দেখবেন 'Inside decorator  ...' প্রিন্ট হয়ে বসে আছে।  তার কারণ, কোড লোড হওয়া মাত্রই সব ডেকোরেটের মেথড আপনা-আপনি কল হয়ে যায়।  আর একারণেই আমরা দেখছি, 'Inside decorator ...' প্রিন্ট আছে।  

  • ৪. পাইথন ডেকোরেটর আর কিছুই না, সূত্র আকারে মনে রাখতে চাইলে:  টার্গেট = ডেকোরেটর(টার্গেট). 

অর্থাৎ টার্গেট ফাঙ্কশনকে ডেকোরেটর ফাঙ্কশনের প্যারামিটার হিসাবে পাঠিয়ে দিয়ে, ডেকোরেটরের নিজস্ব কোড যোগ করে (অর্থাৎ সাজিয়ে নিয়ে) পরে ডেকোরেটর ফাঙ্কশন থেকে কিছু একটা রিটার্ন করিয়ে আবার টার্গেট ফাঙ্কশনের একই নামের ভ্যারিয়েবলে এসাইন করিয়ে দেয়া হয়। চাইলে একই টার্গেট ফাঙ্কশন বা অন্য একটা ইনার ফাঙ্কশন রেফারেন্স রিটার্ন করা যায়।  এতে করে যখন কেউ পরে আবার টার্গেট ফাঙ্কশন কল করবে, টার্গেট() - তখন ডেকোরেটর ফাঙ্কশন থেকে রিটার্ন করা ফাঙ্কশনটা রান করবে।  রামালহোর বইয়ের একটা স্ক্রিনশট না দিয়ে পারছি না :

রামালহোর বইয়ের স্ক্রিনশট: এক কথায় পাইথন ডেকোরেটরের ব্যাখ্যা  


ছবি ৪: ডেকোরেটর মেথডের মাধম্যে টার্গেট মেথড কল করা 

উপরের ছবি ৪ দেখে ব্যাপারটা আরেকটু বুঝিয়ে বলা যাক। খেয়াল করুন, লাইন ২৫ -এ যখন আমরা target() কল করছি, তার আগে কিন্তু 'target' ভ্যারিয়েবলে ডেকোরেটর মেথড থেকে রিটার্ন করা func রেফারেন্স করিয়ে নিচ্ছি। অর্থাৎ, target() কল করলেও আসলে কিন্তু func() ফাঙ্কশন কল হচ্ছে। আর এক্ষেত্রে দুটো একই 'target' ফাঙ্কশন হওয়ায় আউটপুট একই হবে, কিন্তু চাইলেই ডেকোরেটর মেথড থেকে আমরা অন্য inner মেথড রিটার্ন করিয়ে নিয়ে সম্পূর্ণ ভিন্ন কিছু আউটপুট দিতে পারতাম। যেমন নিচের ছবিতে (ছবি ৫), মূল টার্গেট মেথডকে সম্পূর্ণ সরিয়ে দিয়ে 'Inside inner  ...' প্রিন্ট করিয়ে নিচ্ছি:

  

ছবি ৫: টার্গেট মেথডের ব্যবহার (behavior) ডেকোরেটরের inner মেথড দিয়ে পাল্টিয়ে দিলাম 


আর লাইন ২৫ -এর এই কাজটা @ চিহ্ন দিয়ে ডেকোরেটর ফাঙ্কশনের নাম লিখে ব্যাকগ্রাউন্ডে করে ফেলা যায়। যেমন নিচের ছবিতে লাইন ২৫ মুছে দিয়েও একই আউটপুট পাওয়া যাচ্ছে। 

ছবি ৬: ইন্ডিরেক্টলি বা ঘুরিয়ে ডেকোরেটর ফাঙ্কশন কল করা হচ্ছে 

আচ্ছা, কিন্তু এই ঘুরিয়ে কাজ করার দরকার বা উপকারিতাটা কি? ডেকোরেটরের পুরো শক্তি বা উপকারিতা একটা ব্লগ পোস্টে লিখে বোঝাতে গেলে লম্বা হয়ে যাবে। আজকের পোস্টে তাই inner ফাঙ্কশন ব্যবহার না করে, অর্থাৎ মূল টার্গেট মেথড ঠিক রেখে (ছবি ৩- এর মতো) কিভাবে ডেকোরেটর ব্যবহার করে একটা ডিজাইন প্রবলেম সল্ভ করা যায়, সেটা লিখছি। পরে কোনো একদিন বাকি গুলো নিয়ে লিখবো, ইনশাআল্লাহ। 

ধরা যাক ডিজাইন প্রবলেমটা অনেকটা এই রকম : আপনি একটা সুপার স্টোরের চেকআউট সিস্টেম ডিজাইন আর ইমপ্লিমেন্ট করবেন। আইডিয়া হচ্ছে, আপনার স্টোরে কাস্টমারেরা একটা শপিং কার্ট-এ বাজার করে চেকআউট কাউন্টারে আসবে। সব আইটেম স্ক্যান করা হয়ে গেলে আপনি মোট দামের উপর কিছু ডিসকাউন্ট দিতে চান।  যেমন: আপনার 'বান্ধা' বা রেগুলার  কোনো কাস্টমারের জন্য ৫% রিওয়ার্ড ডিসকাউন্ট দিতে চান; কোনো কাস্টমার একই আইটেম ২০ টার বেশি কিনলে সেগুলোর উপর ১০% ডিসকাউন্ট দিতে চান, ইত্যাদি। কিন্তু শর্ত হচ্ছে, একবারে শুধু একটা ডিসকাউন্ট প্রযোজ্য। দুইয়ের বেশি কুপন থাকলে সবচেয়ে বেশি ডিসকাউন্ট যেই কুপনে পাওয়া যাবে, শুধু সেইটাই মূল মূল্যের উপর ছাড় হিসাবে প্রয়োগ হবে। আর আপনি চান, খুব সহজেই পুরোনো কোনো 'অফার' বাদ দিয়ে অন্য নতুন 'অফার' যোগ করবেন (যেমন ঈদে বা পূজা কিংবা বড়দিনের সময় ১০% ছাড়)। এই কোডটা খুব সহজেই পাইথন ডেকোরেটর দিয়ে করে ফেলা যাবে।  

খুব বেশি ব্যাখ্যা না করে সরাসরি 'Cutomer', 'Item' আর 'Cart' ক্লাস গুলো নিচে দিয়ে দিচ্ছি। সাধারণ আইডিয়া, একজন কাস্টমারের নাম, রিওয়ার্ড পয়েন্ট, আর শপিং কার্ট -এর রেফারেন্স থাকবে 'Customer' ক্লাসে।  বাজারের আইটেম-এর ডেসক্রিপশন, দাম, আর সংখ্যা (ডিফল্ট ১ টা) থাকবে 'Item' ক্লাসে।  আর 'Cart' ক্লাসে আইটেমে অবজেক্টের  একটা লিস্ট থাকবে। কাস্টমার বাজার নিয়ে চেকআউট-এ আসলে তার কার্টে থাকা আইটেম গুলোর দাম আর সংখ্যা গুণ করে মোট দাম হিসাব করা হবে।  

ছবি ৭ : সবগুলো ক্লাস একটা পাইথন ফাইলে লিখে রেখেছি 

এইবার, আরেকটা পাইথন ফাইলে সবগুলো 'অফার' মেথড আকারে লিখে রাখবো। মেথডগুলোর কাজ হবে, কাস্টমারকে প্যারামিটার হিসাবে নিয়ে তার শপিং কার্টের আইটেম -র উপর ডিসকাউন্ট হিসাব করা। 

ছবি ৮: ডিসকাউন্ট অফার হিসাব করার ফাঙ্কশনগুলো 

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

দুটো কাজই আসলে ডেকোরেটর দিয়ে করা যায়। একটা ডেকোরেটর ফাঙ্কশন ইমপ্লিমেন্ট করা যায়, যার কাজ হবে ফাঙ্কশনগুলোকে একটা লিস্টে অ্যাড করা।  আর প্রতিটি 'ডিসকাউন্ট' মেথডের শুরুতে @ চিহ্ন দিয়ে ওই ডেকোরেটরের নাম দিয়ে দিলেই হবে। মডিউল ইম্পোর্ট করার সময় (import time) আপনা-আপনি সবগুলো ডেকরেটর মেথড লিস্টে অ্যাড হয়ে যাবে। আর কোনো একটা 'অফার' বাদ দিতে চাইলে শুরুর @ চিহ্ন কমেন্ট আউট (comment out) করে দিলেই চলবে। তখন সেই অফার মেথড আর লিস্টে অ্যাড হবে না।  নিচের কোডে (ছবি ৯) খেয়াল করুন, যেহেতু ডেকোরেটর মেথড টার্গেট মেথড কল করছে না, শুধু রিটার্ন করছে, তাই টার্গেট মেথডের বিহেভিয়ার বা ব্যবহার বদলাচ্ছে না।   

ছবি ৯: ডেকোরেটর ব্যবহার করে promos লিস্টে ডিসকাউন্ট অফার মেথডগুলো যোগ করা হচ্ছে 

খেয়াল করুন, লাইন ২৬ - এ discount মেথড promos লিস্টে থাকা এক একটা ডিসকাউন্ট মেথড কল করবে আর শেষে সর্বোচ্চ ডিসকাউন্ট এমাউন্ট রিটার্ন করবে।  

এইবার, একটা ক্লায়েন্ট কোড লিখে কোডটা রান করা যাক। নিচের ছবি ১০-এ আমরা কয়েকটা আইটেম অবজেক্ট তৈরি করে নিলাম। তারপর একটা Cart অবজেক্ট তৈরি করে আইটেম গুলো অ্যাড করে নিচ্ছি।  এইবার লাইন ১২-তে একজন কাস্টমার 'জসিম' তৈরী করলাম, ধরে নেই উনার রিওয়ার্ড পয়েন্ট ১০০। এবার ডিসকাউন্ট বাদে মূল্য বের করলাম তারপর ডিসকাউন্ট এপ্লাই করলাম।   

ছবি ১০: ডেকোরেটর ব্যবহার করে সর্বোচ্চ ডিসকাউন্ট প্রয়োগ 

খেয়াল করুন, কোড রান করার শুরুতেই ডেকোরেটর গুলো আপনা-আপনি রান হয়ে যাচ্ছে, আর ডিসকাউন্ট প্রমোশন গুলো লিস্টে অ্যাড হয়ে যাচ্ছে।  সবশেষে, লাইন ১৭-তে যখন 'discount()' মেথড কল হচ্ছে, তখন সর্বোচ্চ যেই ডিসকাউন্ট সেটা প্রয়োগ হয়ে দাম দেখাচ্ছে। ব্যস, হয়ে গেলো!!

যাই হোক, আশা করি বুঝতে পেরেছেন। পুরো কোডের Repl.it লিংক নিচে দিয়ে দিচ্ছি।  কোড গুলো ক্লোন করে নিয়ে নিজেরা রান করলে আশাকরি আরো ভালোভাবে বুঝতে পারবেন।  

'ফার্স্ট ক্লাস' ফাঙ্কশন কনসেপ্ট -এর কোড: এই লিংকে 
ডিজাইন প্রবলেম -এর পুরো সল্যুশন কোড : এই লিংকে 

সবাইকে ধন্যবাদ !

কাজের জায়গায় ভুল থেকে শেখা: regex 'র একটা খুব কমন বিষয় যেটা এতদিন ভুল জানতাম

কাজের জায়গায় ভুল থেকে শেখা: regex 'র একটা খুব কমন বিষয় যেটা এতদিন ভুল জানতাম  ৩ ফেব্রুয়ারি, শনিবার, ২০২৪ রেগুলার এক্সপ্রেশন (Regular Exp...