लर्निंग पाथ
प्रेरणा
आप शायद पहले से with स्टेटमेंट से परिचित होंगे, जो Python में संसाधनों को संभालने का संक्षिप्त तरीका है। लेकिन क्या आपने कभी सोचा है कि यह परदे के पीछे कैसे काम करता है? दरअसल, with कीवर्ड की शक्ति context managers से आती है।
Context managers Python का एक बुनियादी डिजाइन पैटर्न हैं जो संसाधन प्रबंधन के लिए एक संरचित तरीका देते हैं। ये सुनिश्चित करते हैं कि संसाधन प्राप्त किए जाएँ, सही तरीके से उपयोग हों, और अंत में—चाहे त्रुटि या अपवाद ही क्यों न हो—रिलीज़ या साफ कर दिए जाएँ।
यह खास तौर पर तब महत्वपूर्ण होता है जब आप फाइलों, नेटवर्क कनेक्शनों, या डेटाबेस हैंडल्स जैसे संसाधनों से निपटते हैं। Context managers का उपयोग करके, हम साफ-सुथरा और अधिक भरोसेमंद कोड लिख सकते हैं और फाइल बंद करना या लॉक रिलीज़ करना भूल जाने की चिंता से मुक्त हो सकते हैं।
इस ट्यूटोरियल में, हम Python के डिफ़ॉल्ट context managers से आगे बढ़कर अपने खुद के लिखना सीखेंगे।
Python में Context Managers को समझना
अंदरूनी तौर पर, context managers ऐसे ऑब्जेक्ट होते हैं जो दो विशेष मेथड्स परिभाषित करते हैं: __enter__ और __exit__। __enter__ मेथड तब कॉल होता है जब आप with ब्लॉक में प्रवेश करते हैं, और उसका रिटर्न मान उस ब्लॉक के भीतर एक वेरिएबल को असाइन हो जाता है। दूसरी ओर, __exit__ मेथड तब कॉल होता है जब with ब्लॉक से बाहर निकलते हैं—चाहे वह सामान्य रूप से समाप्त हो या किसी अपवाद के साथ।
यह संरचना उचित संसाधन हैंडलिंग सुनिश्चित करती है। इसे बेहतर समझने के लिए Python के कुछ बिल्ट-इन context managers देखें:
फाइल खोलने का क्लासिक उदाहरण लें:
with open('file.txt', 'r') as file:
data = file.read()
यहाँ, open('file.txt', 'r') context manager की तरह काम करता है। जब आप with ब्लॉक में प्रवेश करते हैं, तो फाइल ऑब्जेक्ट का enter मेथड कॉल होता है, जो फाइल खोलता है और उसे file वेरिएबल को असाइन कर देता है। फिर आप file.read() का उपयोग करके फाइल की सामग्री एक्सेस कर सकते हैं।
इसके बाद, with ब्लॉक से बाहर निकलते समय फाइल ऑब्जेक्ट का __exit__ मेथड कॉल होना सुनिश्चित होता है, भले ही कोई अपवाद हो। यह मेथड फाइल बंद करने का ध्यान रखता है, ताकि खुले फाइल हैंडल्स इधर-उधर न छूट जाएँ।
2. थ्रेड लॉकिंग
फाइलों से आगे बढ़कर, context managers को threading.Lock() का उपयोग कर थ्रेड सिंक्रोनाइज़ेशन के लिए भी इस्तेमाल किया जा सकता है:
import threading
lock = threading.Lock()
with lock:
# Critical section
print("This code is executed under lock protection.")
यहाँ, lock एक threading.Lock ऑब्जेक्ट है, जो एक और context manager है। जब आप with ब्लॉक में प्रवेश करते हैं, तो __enter__ मेथड लॉक को acquire करता है, जिससे एक समय में केवल एक थ्रेड ही क्रिटिकल सेक्शन चला सके। अंत में, with ब्लॉक से बाहर निकलते समय __exit__ मेथड लॉक को रिलीज़ कर देता है, जिससे अन्य थ्रेड आगे बढ़ सकें।
3. डेटाबेस कनेक्शन्स
इसी तरह, context managers डेटाबेस कनेक्शनों को भी मैनेज कर सकते हैं:
import sqlite3
with sqlite3.connect('database.db') as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM table")
rows = cursor.fetchall()
sqlite3.connect('database.db') कॉल एक context manager है। with ब्लॉक में प्रवेश करते ही डेटाबेस से कनेक्शन स्थापित होता है और connection को असाइन हो जाता है। फिर आप cursor की मदद से डेटाबेस के साथ इंटरैक्ट कर सकते हैं। with ब्लॉक से बाहर निकलते समय __exit__ मेथड कनेक्शन को बंद करना सुनिश्चित करता है, जिससे संसाधन लीक न हों।
4. नेटवर्क सॉकेट्स
Context managers नेटवर्क संचार को भी संभाल सकते हैं:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('localhost', 8080))
s.sendall(b'Hello, world')
data = s.recv(1024)
यहाँ, socket.socket(socket.AF_INET, socket.SOCK_STREAM) एक सॉकेट ऑब्जेक्ट बनाता है जो context manager की तरह काम करता है। __enter__ मेथड सॉकेट बनाता है, और with ब्लॉक के भीतर आप कनेक्ट कर सकते हैं, डेटा भेज सकते हैं और प्राप्त कर सकते हैं। __exit__ मेथड सुनिश्चित करता है कि काम पूरा होने पर सॉकेट सही तरीके से बंद हो जाए।
5. डायरेक्टरी स्कैनिंग
इस बार, os.scandir('.') डायरेक्टरी एंट्रीज़ पर इटरेट करने का तरीका देता है:
with os.scandir('.') as entries:
for entry in entries:
print(entry.name)
os.scandir('.') यहाँ context manager की तरह काम करता है। __enter__ मेथड डायरेक्टरी स्कैन खोलता है, और आप with ब्लॉक के भीतर एंट्रीज़ पर इटरेट कर सकते हैं। __exit__ मेथड बाहर निकलते समय स्कैन को साफ कर देता है।
जैसा कि आप देख सकते हैं, context managers द्वारा संचालित with स्टेटमेंट संसाधनों के आबंटन और विमोचन को स्वतः संभालकर प्रबंधन को सरल बना देता है।
Python में कस्टम context managers लिखना
अब, आइए देखें कि संसाधन प्रबंधन पर सूक्ष्म नियंत्रण पाने के लिए आप अपने कस्टम मैनेजर्स कैसे लिख सकते हैं।
कस्टम context managers लिखने के दो मुख्य तरीके हैं: क्लास-आधारित और फ़ंक्शन-आधारित।
क्लास-आधारित तरीका
क्लास-आधारित तरीका context managers लिखने का सबसे संरचित और लचीला तरीका है। यहाँ, आप एक क्लास परिभाषित करते हैं जो विशेष मेथड्स __enter__ और __exit__ को इम्प्लिमेंट करता है।
आइए एक Timer क्लास का उदाहरण देखें जो निष्पादन समय मापता है:
import time
class Timer:
def __enter__(self):
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.end_time = time.time()
elapsed_time = self.end_time - self.start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with Timer() as timer:
# Code block to measure the execution time
time.sleep(2) # Simulate some time-consuming operation
Elapsed time: 2.002082347869873 seconds
यहाँ, Timer क्लास with ब्लॉक में प्रवेश करते समय __enter__ मेथड में प्रारंभ समय कैप्चर करता है। यह self लौटाता है ताकि ब्लॉक के भीतर ऑब्जेक्ट की पहुँच बनी रहे। with ब्लॉक से बाहर निकलते समय __exit__ मेथड बीता हुआ समय गणना कर उसे प्रिंट करता है।
फ़ंक्शन-आधारित तरीका
यदि आप अधिक संक्षिप्त तरीका पसंद करते हैं—संभवतः लचीलेपन की कुछ कीमत पर—तो फ़ंक्शन-आधारित तरीका अच्छा विकल्प हो सकता है। इसमें contextlib मॉड्यूल के contextmanager डेकोरेटर का उपयोग कर किसी भी फ़ंक्शन को context manager में बदला जाता है।
आइए देखें यह कैसे काम करता है:
import time
from contextlib import contextmanager
@contextmanager
def timer():
start_time = time.time()
yield
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with timer():
time.sleep(2)
Elapsed time: 2.0020740032196045 seconds
@contextmanager डेकोरेटर timer फ़ंक्शन को context manager में बदल देता है। फ़ंक्शन के अंदर start_time कैप्चर होता है, और yield स्टेटमेंट निष्पादन रोक देता है ताकि with ब्लॉक के भीतर का कोड चल सके।
अंत में, __exit__ जैसी कार्यक्षमता अंत समय कैप्चर कर बीता हुआ समय प्रिंट करके हासिल की जाती है।
मूल रूप से, आप yield से पहले __enter__ की लॉजिक लिखते हैं और उसके बाद __exit__ की। दोनों तरीके एक ही परिणाम देते हैं; चुनाव आपके संरचना और पठनीयता के प्रति झुकाव पर निर्भर करता है।
Context Managers के व्यावहारिक उदाहरण
अब जब आप Python में कस्टम context managers लिखना समझ चुके हैं, तो आइए कुछ व्यावहारिक उदाहरण देखें जहाँ ये बेहद उपयोगी हो सकते हैं।
फ़ाइल ऑपरेशन्स का प्रबंधन
फाइल ऑपरेशन्स कई अनुप्रयोगों में आम कार्य हैं। फाइलों को पढ़ना या लिखना—दोनों में उचित प्रबंधन डेटा की अखंडता और संसाधन दक्षता सुनिश्चित करता है। मान लें कि हम कस्टम context manager का उपयोग करके किसी फाइल में टेक्स्ट लिखना चाहते हैं।
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
# Example usage
if __name__ == "__main__":
with FileManager("example.txt", "w") as file:
file.write("Hello, world!\n")
इस उदाहरण में, हम एक FileManager क्लास परिभाषित करते हैं जो Python के open फ़ंक्शन की तरह फाइल ऑपरेशन्स संभालता है। आइए देखें प्रत्येक मेथड क्या करता है:
__init__(self, filename, mode): यह क्लास का कन्स्ट्रक्टर है। यह दिए गए filename और mode के साथFileManagerऑब्जेक्ट को इनिशियलाइज़ करता है, जो यह निर्दिष्ट करते हैं कि कौन सी फाइल और किस मोड (जैसे, पढ़ना, लिखना, जोड़ना) में खोली जाए।__enter__(self): यहwithब्लॉक में प्रवेश करते ही कॉल होता है। यहfilenameऔरmodeके अनुसार फाइल खोलता है। खोला गया फाइल ऑब्जेक्टself.fileको असाइन होता है औरwithब्लॉक के भीतर उपयोग के लिए रिटर्न किया जाता है।__exit__(self, exc_type, exc_value, traceback): यहwithब्लॉक से बाहर निकलते समय—चाहे अपवाद हुआ हो या नहीं—कॉल होता है। यदिself.fileNoneनहीं है तोself.file.close()कॉल कर फाइल सही तरह से बंद करना सुनिश्चित करता है। इससे संसाधन लीक रुकते हैं और उचित क्लीनअप होता है।
यदि आप ऊपर का कोड चलाने के बाद example.txt की सामग्री देखें, तो लिखा हुआ टेक्स्ट दिखेगा:
$ cat example.txt
Hello, world!
बेशक, FileManager क्लास का लाभ यह है कि आप फाइलों के साथ काम करने के लिए कोई भी कस्टम लॉजिक इम्प्लिमेंट कर सकते हैं। उदाहरण के लिए, आप इसे इस तरह संशोधित कर सकते हैं कि यह कौन-सी फाइलें खोली/बंद की गईं, इसका लॉग एक टेक्स्ट फाइल में रखे।
import datetime
class FileManager:
def __init__(self, filename, mode, log_filename="file_log.txt"):
self.filename = filename
self.mode = mode
self.log_filename = log_filename
self.file = None
def log_action(self, action):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.log_filename, "a") as log_file:
log_file.write(f"{timestamp} - {action}: {self.filename}\n")
def __enter__(self):
self.file = open(self.filename, self.mode)
self.log_action("Opened")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
self.log_action("Closed")
# Example Usage
with FileManager("example.txt", "r") as file:
content = file.read()
print(content)
Hello, world!
इस संशोधित संस्करण में:
datetimeमॉड्यूल टाइमस्टैम्प के लिए इम्पोर्ट किया गया है।log_actionमेथड फाइल की क्रियाओं को लॉग करने के लिए जोड़ा गया है।log_filenameपैरामीटर लॉग फाइल के नाम को निर्दिष्ट करता है; डिफ़ॉल्ट'file_log.txt'है।__enter__और__exit__के भीतरlog_actionमेथड से फाइलनाम और क्रिया (opened/closed) लॉग फाइल में जोड़ दी जाती है।log_actionमेंdatetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")का उपयोग"YYYY-MM-DD HH:MM:SS"प्रारूप का वर्तमान टाइमस्टैम्प पाने के लिए किया गया है।- टाइमस्टैम्प को फाइलनाम और क्रिया के साथ लॉग में शामिल किया जाता है।
अब, यदि हम file_log.txt की सामग्री प्रिंट करें, तो लॉग दिखेंगे:
$ cat file_log.txt
2024-04-03 12:37:02 - Opened: example.txt
2024-04-03 12:37:02 - Closed: example.txt
एक और व्यावहारिक उपयोग-मामला जिसे हम कस्टम context manager के रूप में इम्प्लिमेंट कर सकते हैं, वह है SQLite डेटाबेस मैनेजमेंट टूल:
import sqlite3
class DatabaseConnection:
def __init__(self, database_name):
self.database_name = database_name
self.connection = None
def __enter__(self):
self.connection = sqlite3.connect(self.database_name)
return self.connection
def __exit__(self, exc_type, exc_value, traceback):
if self.connection:
self.connection.close()
यह फिर से sqlite3.connect फ़ंक्शन की नकल करता है, लेकिन इसमें कस्टम डेटाबेस मैनेजमेंट लॉजिक जोड़कर इसे और बेहतर बनाया जा सकता है। चलिए कुछ सैंपल फ़ंक्शन्स से जाँचते हैं कि मैनेजर काम कर रहा है या नहीं:
# Example usage
def create_table():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
email TEXT)"""
)
def insert_data(username, email):
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"INSERT INTO users (username, email) VALUES (?, ?)", (username, email)
)
connection.commit()
def fetch_data():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
return cursor.fetchall()
if __name__ == "__main__":
create_table()
insert_data("john_doe", "john@example.com")
insert_data("jane_doe", "jane@example.com")
users = fetch_data()
print("Users in the database:")
for user in users:
print(user)
Users in the database:
(1, 'john_doe', 'john@example.com')
(2, 'jane_doe', 'jane@example.com')
(3, 'john_doe', 'john@example.com')
(4, 'jane_doe', 'jane@example.com')
हम डेटाबेस ऑपरेशन्स को फ़ंक्शन्स में तोड़कर और अपने बनाए कस्टम मैनेजर का उपयोग करके कहीं अधिक संभालने योग्य कोड लिख सकते हैं।
Python में Context Managers से जुड़े उन्नत विषय
इस खंड में, हम Python में context managers से जुड़े उन्नत विषयों पर चर्चा करेंगे। इनमें एरर हैंडलिंग और context managers का nesting शामिल है।
एरर हैंडलिंग
एरर हैंडलिंग प्रोग्रामिंग का एक अनिवार्य पक्ष है, और Python के context managers संसाधन प्रबंधन कार्यों के भीतर त्रुटियों को सुविधाजनक ढंग से संभालने का तरीका देते हैं।
FileManager क्लास के उदाहरण में, __exit__ मेथड के भीतर एरर हैंडलिंग इम्प्लिमेंट की जा सकती है ताकि अपवाद आने पर भी सही क्लीनअप हो। उदाहरण के लिए, यदि फाइल में डेटा लिखते समय (with ब्लॉक के दौरान) कोई त्रुटि होती है, तो भी हम फाइल बंद करना सुनिश्चित करना चाहेंगे ताकि संसाधन लीक न हों। हम __exit__ मेथड को इस तरह संशोधित कर सकते हैं:
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
if exc_type is not None:
print(f"An error occurred: {exc_value}")
यहाँ, हम पहले self.file.close() के जरिए सही क्लीनअप सुनिश्चित करते हैं। फिर जाँचते हैं कि कोई अपवाद हुआ था या नहीं (exc_type is not None)। यदि हुआ था, तो क्या गड़बड़ हुई इस पर फीडबैक देने के लिए एक एरर संदेश प्रिंट करते हैं।
यहाँ:
exc_typeअपवाद क्लास है, जैसेAttributeErrorऔरIndexError।exc_valueसंदेश सहित अपवाद इंस्टेंस है।tracebackत्रुटि का पूर्ण traceback है।
आइए जानबूझकर गलत कोड के साथ FileManager क्लास चलाएँ:
with FileManager("example.txt", "r") as file:
# Simulate a sample error
content = file.read()
print(content[150])
An error occurred: string index out of range
तो, अपने कस्टम मैनेजर्स में अपवादों को संभालते समय कस्टम लॉजिक इम्प्लिमेंट करने के लिए, आप यह नमूना वर्कफ़्लो अपना सकते हैं:
def __exit__(self, exc_type, exc_value, traceback):
# Perform cleanup
...
# Handle the exception
if isinstance(exc_value, AttributeError):
# Handle AttributeError here
print(f"Exception message: {exc_value}")
# Handle another exception
elif isinstance(exc_value, AnotherError):
...
Context managers का nesting
Context managers का nesting आपको एक ही with स्टेटमेंट के भीतर एकाधिक संसाधनों को, प्रत्येक के अपने context manager के साथ, एक साथ मैनेज करने देता है। यह खास तौर पर तब उपयोगी है जब जटिल ऑपरेशन्स में कई संसाधन शामिल होते हैं। आइए अपने उदाहरणों में context managers को nest करना देखें।
मान लें कि हम एक फाइल खोलना और उसी संदर्भ में डेटाबेस ऑपरेशन्स करना चाहते हैं। मसलन, हम किसी CSV फाइल की सामग्री को डेटाबेस में पंक्तियों के रूप में डालना चाहते हैं। यह हम FileManager और DatabaseConnection context managers को एक ही with स्टेटमेंट में nest करके कर सकते हैं:
if __name__ == "__main__":
with FileManager("example.txt", "w") as file, DatabaseConnection("example.db") as connection:
...
इस उदाहरण में, FileManager और DatabaseConnection context managers एक ही with स्टेटमेंट में nested हैं। इससे हम फाइल खोलने और डेटाबेस कनेक्शन स्थापित करने दोनों को एक साथ कर पाते हैं। इसके बाद के फाइल-लेखन और डेटाबेस ऑपरेशन्स इसी संयुक्त संदर्भ में किए जाते हैं।
एक बात ध्यान में रखें—कोड की पठनीयता। मैं सिफारिश करूँगा कि एक साथ चल रहे contexts की संख्या दो से अधिक न हो।
निष्कर्ष
Python में context managers with स्टेटमेंट के भीतर संसाधन प्रबंधन के लिए एक संरचित तरीका देते हैं, जो संसाधनों के उचित आबंटन और विमोचन को सुनिश्चित करता है।
इस ट्यूटोरियल में, हमने context managers की मूल बातें सीखीं, व्यावहारिक उदाहरण देखे, और एरर हैंडलिंग व nesting जैसे उन्नत विषयों पर चर्चा की। कस्टम context managers लिखने में महारत हासिल कर, आप अधिक साफ, अधिक भरोसेमंद कोड लिख सकते हैं, एरर हैंडलिंग सुधार सकते हैं, और संसाधनों का कुशलता से प्रबंधन कर सकते हैं।
कस्टम context managers लिखने का विषय “Advanced Python” श्रेणी के अंतर्गत आता है। अपनी क्षमताओं को और निखारने के लिए ये संबंधित संसाधन देखें:
मैं 2 से अधिक वर्षों के अनुभव वाला डेटा साइंस कंटेंट क्रिएटर हूँ और Medium पर सबसे बड़े फॉलोइंग्स में से एक रखता हूँ। मुझे एआई और एमएल पर विस्तार से लेख लिखना पसंद है, जिसमें हल्का-सा व्यंग्यात्मक अंदाज रहता है—क्योंकि कुछ तो करना पड़ता है ताकि वे कम उबाऊ लगें। मैंने 130 से अधिक लेख और एक DataCamp कोर्स तैयार किया है, और एक और बन रहा है। मेरा कंटेंट 50 लाख से अधिक लोगों ने देखा है, जिनमें से 20 हज़ार लोग Medium और LinkedIn पर फॉलोअर्स बन गए।
