যারা তাদের প্রথম Python টিউটোরিয়ালটি খোলেন, তাদের বেশিরভাগই সরাসরি ভেরিয়েবল, লুপ এবং কিছু বাস্তবসম্মত কিছু তৈরি করার দিকে যেতে চান। এই প্রবণতাটি বোধগম্য। কিন্তু আপনি যদি থামেন এবং বোঝার চেষ্টা করেন যে Python আসলে কী এবং এটি এর নিচের মেশিনের সাথে কীভাবে সম্পর্কিত, তবে আপনি আপনার ভবিষ্যতের কোড অনেক কম বিভ্রান্তির সাথে ডিবাগ করতে পারবেন। প্রোগ্রামিং ভাষাগুলো সব এক নয়। তারা বিমূর্ততার (abstraction) বিভিন্ন স্তরে অবস্থান করে, বিভিন্নভাবে সুবিধার জন্য নিয়ন্ত্রণের সাথে আপস করে এবং বিভিন্ন পথের মাধ্যমে প্রসেসরের কাছে পৌঁছায়। Python সেই ইকোসিস্টেমের একটি খুব নির্দিষ্ট স্থানে অবস্থান করে। সেই স্থানটি বোঝা প্রোগ্রামিং শেখার প্রথম প্রকৃত পদক্ষেপ।

ভাষার শ্রেণিবিন্যাস: Python যেখানে অবস্থান করে

হার্ডওয়্যারের সাথে তাদের নৈকট্যের ভিত্তিতে প্রোগ্রামিং ভাষাকে মূলত তিনটি ভাগে ভাগ করা যায়।

High-level languages সিলিকন থেকে সবচেয়ে দূরে অবস্থান করে। Python এখানে Java এবং JavaScript-এর পাশাপাশি অবস্থান করে। এই ভাষাগুলো এমন সিনট্যাক্স ব্যবহার করে যা মানুষের ভাষার মতো। মেমরি অ্যাড্রেস এবং বাইনারি ইনস্ট্রাকশন নিয়ে লড়াই করার পরিবর্তে আপনি user_count = 5 বা print("Hello") লিখবেন। যেহেতু এগুলো CPU, মেমরি ম্যানেজমেন্ট এবং চিপসেটের পার্থক্যের বিবরণগুলোকে বিমূর্ত (abstract) করে দেয়, তাই একই হাই-লেভেল কোড খুব সামান্য বা কোনো পরিবর্তন ছাড়াই প্রায়শই একটি Mac, Windows PC বা Linux সার্ভারে চলতে পারে।

এই পোর্টেবিলিটির একটি মূল্য দিতে হয়। High-level languages-এর জন্য একজন অনুবাদকের প্রয়োজন হয়। এগুলো সরাসরি প্রসেসরে চলতে পারে না। আপনার পঠনযোগ্য কোড এবং মেশিনের বৈদ্যুতিক সংকেতের মধ্যে ব্যবধান দূর করতে আপনার একটি compiler অথবা interpreter-এর প্রয়োজন। এর সুবিধা হলো ডেভেলপমেন্টের গতি। আপনি সরাসরি হার্ডওয়্যার নিয়ন্ত্রণ ত্যাগ করেন যাতে প্রথম দিন থেকেই দরকারী প্রোগ্রাম লিখতে পারেন।

Low-level languages ঠিক বিপরীত প্রান্তে অবস্থান করে। এগুলো মূলত মেশিন কোড — এক এবং শূন্যের সেই কাঁচা অনুক্রম (raw sequences) যা প্রসেসর সরাসরি বুঝতে পারে। মেশিন কোড লেখার অর্থ হলো চিপটির মতো করে চিন্তা করা। আপনি ঠিকভাবে সিদ্ধান্ত নেন কোন মেমরি অ্যাড্রেস অ্যাক্সেস করা হবে এবং কোন CPU register একটি নির্দিষ্ট মান ধারণ করবে। হার্ডওয়্যার তাৎক্ষণিকভাবে এবং কোনো অনুবাদ সংক্রান্ত অতিরিক্ত ঝামেলা (overhead) ছাড়াই নির্দেশ পালন করে।

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

Assembly languages একটি সংকীর্ণ মধ্যবর্তী অবস্থানে থাকে। এগুলো বাইনারি ইনস্ট্রাকশনকে mnemonics নামক ছোট মানুষের পাঠযোগ্য প্রতীকের মাধ্যমে প্রতিস্থাপন করে। এক এবং শূন্যের একটি স্ট্রিংয়ের পরিবর্তে, আপনি ডেটা মুভ করার জন্য MOV বা যোগ করার জন্য ADD লিখতে পারেন। এই প্রতীকগুলো কাঁচা বাইনারির চেয়ে মনে রাখা সহজ, তবে এগুলো একটি নির্দিষ্ট প্রসেসর আর্কিটেকচারের সাথে ঘনিষ্ঠভাবে যুক্ত থাকে। একটি Intel x86 চিপের জন্য লেখা অ্যাসেম্বলি প্রোগ্রাম একটি ARM প্রসেসরে চলবে না।

একটি assembler এই mnemonics গুলোকে মেশিন কোডে রূপান্তরিত করে। Assembly প্রোগ্রামারদের Python-এর চেয়ে অনেক বেশি নিয়ন্ত্রণ দেয়, তবে এর জন্য প্রসেসরের অভ্যন্তরীণ কার্যপ্রণালী সম্পর্কে গভীর জ্ঞান প্রয়োজন। এটি বাইনারির চেয়ে মানুষের চিন্তার কাছাকাছি, তবুও এটি প্রসেসরের নিজস্ব ভাষায় কথা বলে।

কোড কীভাবে কাজে রূপান্তরিত হয়

প্রতিটি প্রোগ্রামকে শেষ পর্যন্ত মেশিন ইনস্ট্রাকশনে রূপান্তরিত হতে হয়। সোর্স কোড থেকে চলন্ত অ্যাপ্লিকেশনে পৌঁছানোর পথটি দুটি কৌশলের যেকোনো একটি অনুসরণ করে।

একটি compiler আপনার সম্পূর্ণ কোডবেসকে একবারে অনুবাদ করে। আপনি যদি তাকে একশ লাইনের একটি ফাইল দেন, তবে এটি কিছু চালানোর চেষ্টা করার আগে সমস্ত একশ লাইন পড়ে এবং বিশ্লেষণ করে। এটি পুরো প্রোগ্রামে সিনট্যাক্স এরর (syntax errors) খোঁজে। পঞ্চাশ নম্বর লাইনে একটি টাইপো পেল? কম্পাইলার থেমে যাবে, সমস্যাটি রিপোর্ট করবে এবং আপনি এটি ঠিক না করা পর্যন্ত একটি রানযোগ্য প্রোগ্রাম তৈরি করতে অস্বীকার করবে।

C এবং C++ এর মতো ভাষাগুলো এই পদ্ধতি ব্যবহার করে। এর ফলাফল সাধারণত একটি স্ট্যান্ডঅ্যালোন এক্সিকিউটেবল (executable) ফাইল যা দ্রুত গতির জন্য অপ্টিমাইজ করা। যেহেতু কম্পাইলার শুরুতেই পুরো কোডবেসটি পুঙ্খানুপুঙ্খভাবে পরীক্ষা করে, তাই প্রোগ্রামটি চালু হওয়ার আগেই এটি একগুচ্ছ এরর শনাক্ত করতে পারে। এর বিনিময়ে কিছুটা অসুবিধা তৈরি হয়। এডিট-কম্পাইল-রান চক্রটি সময়সাপেক্ষ। একটি লাইন পরিবর্তন করলে আপনাকে পুরো প্রজেক্টটি পুনরায় তৈরি (rebuild) হওয়ার জন্য অপেক্ষা করতে হতে পারে।

একটি interpreter সম্পূর্ণ ভিন্ন পদ্ধতি অবলম্বন করে। এটি আপনার কোড লাইন বাই লাইন পড়ে, এবং প্রতিটি স্টেটমেন্ট পড়ার সাথে সাথে তা অনুবাদ ও কার্যকর করে। এটি পুরো ফাইলটি পরীক্ষা শেষ হওয়ার জন্য অপেক্ষা করে না। Python REPL-এ একটি কমান্ড টাইপ করুন, Enter চাপুন, এবং ইন্টারপ্রেটার সেই একটি লাইন প্রসেস করবে, সেটিকে ইনস্ট্রাকশনে রূপান্তর করবে এবং তাৎক্ষণিকভাবে তা চালিয়ে দেবে।

এটি ডিবাগিংয়ের ধরন বদলে দেয়। একটি ইন্টারপ্রেটারের ক্ষেত্রে, ত্রুটিগুলো তখনই প্রকাশ পায় যখন ইন্টারপ্রেটারটি সমস্যাযুক্ত লাইনে পৌঁছায়, তার আগে নয়। আপনার প্রোগ্রামটি হয়তো আশিটি লাইন পর্যন্ত নিখুঁতভাবে চলতে পারে এবং তারপর ৮১ নম্বর লাইনে গিয়ে ক্র্যাশ করতে পারে। এই তাৎক্ষণিকতা ইন্টারপ্রেটারকে শেখার জন্য আরও সহজবোধ্য করে তোলে। আপনি পরীক্ষা-নিরীক্ষা করতে পারেন, ফলাফল দেখতে পারেন এবং রিয়েল-টাইমে তা সমন্বয় করতে পারেন। পাইথনের স্ট্যান্ডার্ড ইমপ্লিমেন্টেশন, CPython, আসলে একটি হাইব্রিড মডেল ব্যবহার করে: এটি আপনার সোর্স কোডকে বাইটকোডে (bytecode) কম্পাইল করে, তারপর একটি ভার্চুয়াল মেশিনের মাধ্যমে সেই বাইটকোডটি এক্সিকিউট করে। যদিও পর্দার আড়ালে একটি ট্রান্সলেশন ধাপ রয়েছে, তবুও এর কার্যকারিতা ইন্টারঅ্যাক্টিভ এবং লাইন-বাই-লাইন বলে মনে হয়।

কেন পাইথনকে স্ক্রিপ্টিং ল্যাঙ্গুয়েজ বলা হয়

পাইথনকে প্রায়ই একটি স্ক্রিপ্টিং ল্যাঙ্গুয়েজ হিসেবে বর্ণনা করা হয়। এই পরিচয়টি এর উৎস এবং সাধারণ ব্যবহারের ক্ষেত্রগুলোকে প্রতিফলিত করে। আপনি একটি ছোট ফাইল — একটি স্ক্রিপ্ট — লেখেন যা কোনো কাজকে স্বয়ংক্রিয় করে, টেক্সট ম্যানিপুলেট করে অথবা আলাদা আলাদা প্রোগ্রামকে একত্রে জুড়ে দেয়, এবং আপনি সরাসরি এটি ইনভোক করেন। ইন্টারপ্রেটার তাৎক্ষণিকভাবে (on the fly) ট্রান্সলেশন সম্পন্ন করে। এখানে ম্যানেজ করার জন্য আলাদা কোনো কম্পাইলেশন ধাপ নেই, কিংবা ট্র্যাক করার জন্য কোনো build artifacts নেই।

স্ক্রিপ্টিং ল্যাঙ্গুয়েজ এবং জেনারেল-পারপাস প্রোগ্রামিং ল্যাঙ্গুয়েজের মধ্যকার সীমারেখা এখন অনেকটাই অস্পষ্ট হয়ে গেছে। পাইথন এখন বিশাল ওয়েব অ্যাপ্লিকেশন, ডেটা সায়েন্স পাইপলাইন এবং মেশিন লার্নিং সিস্টেম পরিচালনা করছে। তবুও, মূল ধারণাটি অপরিবর্তিত রয়েছে। আপনি একটি বিল্ড সিস্টেম ম্যানেজ করার চেয়ে সমস্যা সমাধানের দিকে বেশি মনোযোগ দেন। আপনি নির্দেশ দেওয়ার মুহূর্তেই ইন্টারপ্রেটার তা কার্যকর করার জন্য প্রস্তুত থাকে।

একটি দীর্ঘস্থায়ী ভিত্তি তৈরি করা

এই পার্থক্যগুলো কেবল তাত্ত্বিক trivia নয়। পাইথন শেখার প্রথম সপ্তাহে আপনি যে ধরনের আচরণের সম্মুখীন হবেন, এগুলো তারই ব্যাখ্যা দেয়। যখন পাইথন এক্সিকিউশনের সময় একটি SyntaxError দেখায়, তখন আপনি বুঝতে পারবেন যে ইন্টারপ্রেটার এমন একটি লাইনে পৌঁছেছে যা সে অনুবাদ করতে পারেনি। যখন আপনি পড়বেন যে নির্দিষ্ট কিছু কাজের জন্য পাইথন C-এর চেয়ে ধীরগতির, তখন আপনি ইন্টারপ্রিটেশন এবং হাই-লেভেল অ্যাবস্ট্রাকশনের কারণে সৃষ্ট ওভারহেড (overhead) বিষয়টি বুঝতে পারবেন। যখন আপনি আপনার স্ক্রিপ্টের পাশে .pyc ফাইল দেখতে পাবেন, তখন আপনি বুঝতে পারবেন যে পাইথন কম্পাইল করা বাইটকোড ক্যাশ (cache) করে রাখছে যাতে প্রতিবার চালানোর সময় আপনার টেক্সট ফাইলটি পুনরায় ইন্টারপ্রেট করতে না হয়।

ল্যাঙ্গুয়েজ হায়ারার্কিতে পাইথনের অবস্থান জানা থাকলে পরবর্তীতে সঠিক টুল বেছে নিতেও সাহায্য করে। এমন একটি ডিভাইস ড্রাইভার লিখতে হবে যেখানে প্রতিটি CPU সাইকেল গুরুত্বপূর্ণ? সেক্ষেত্রে আপনি সম্ভবত C বা assembly ব্যবহার করবেন। একটি বিকেলের মধ্যেই একটি CSV ফাইল প্রসেস করতে হবে বা একটি ওয়েব API তৈরি করতে হবে? পাইথনের ইন্টারপ্রেটার এবং সহজবোধ্য সিনট্যাক্স ঠিক এই কাজের জন্যই তৈরি করা হয়েছে।

মূল সারসংক্ষেপ

পাইথনের শক্তি আসে এর অবস্থান থেকে। এটি হার্ডওয়্যারের অনেক উপরে অবস্থান করে এবং এমন একটি ইন্টারপ্রেটার দ্বারা অনুবাদিত হয় যা মেশিনের গতির চেয়ে প্রোগ্রামারের গতির ওপর বেশি গুরুত্ব দেয়। এই পটভূমি সম্পর্কে না জেনেও আপনি সিনট্যাক্স শিখতে পারেন, কিন্তু নিচের এই মেকানিজম বা কার্যপ্রণালী না বোঝা পর্যন্ত আপনি বুদ্ধিমত্তার সাথে ডিবাগ করতে বা স্বজ্ঞাতভাবে (intuitively) অপ্টিমাইজ করতে পারবেন না। এই মৌলিক বিষয়গুলো দিয়ে শুরু করুন। যখন আপনি আপনার প্রথম আসল প্রোগ্রামটি লিখবেন, তখন আপনি কেবল কমান্ড টাইপ করবেন না; বরং আপনি জানবেন ঠিক কীভাবে সেগুলো মেশিনের কাছে পৌঁছায়।