Python descriptors, या सामान्य रूप से कहें तो descriptors, आपको ऐसा शक्तिशाली तरीका देते हैं जिससे आप पुन: प्रयोज्य कोड लिख सकें जो कक्षाओं (classes) के बीच साझा किया जा सके। वे इनहेरिटेंस की अवधारणा जैसे लग सकते हैं, लेकिन तकनीकी रूप से वे अलग हैं। ये गुण (attribute) एक्सेस को इंटरसेप्ट करने का एक सामान्य-उद्देश्य तरीका हैं। प्रॉपर्टीज, स्टैटिक मेथड्स, क्लास मेथड्स, सुपर मेथड्स आदि के पीछे descriptors ही मैकेनिज़्म है।
Descriptors को Python संस्करण 2.2 में जोड़ा गया था, और तब से इन्हें जादुई चीज़ों के रूप में माना गया है जिन्होंने पारंपरिक कक्षाओं को एक नया स्टाइल दिया। ये ऐसी कक्षाएँ हैं जो आपको किसी दूसरी कक्षा में प्रबंधित प्रॉपर्टीज (managed properties) लागू करने देती हैं। विशेष रूप से, वे __get__(), __set__() और __delete__() मेथड के लिए इंटरफेस लागू करती हैं, जो कई कारणों से उन्हें रोचक बनाते हैं। उदाहरण के लिए, Python में आपने पहले जो class decorator और property decorator देखे होंगे।
सरल शब्दों में, कोई भी कक्षा जो किसी ऑब्जेक्ट के लिए __get__(), __set()__, या __delete()__ मेथड लागू करती है, उसे "Descriptor" कहा जाता है। सीधे Python के आधिकारिक प्रलेखन से उद्धृत करें तो, एक descriptor वह ऑब्जेक्ट एट्रिब्यूट है जिसमें binding behavior होता है, यानी जिसका एट्रिब्यूट एक्सेस descriptor प्रोटोकॉल के मेथड्स द्वारा ओवरराइड किया गया हो। ये मेथड्स हैं __get__(), __set__(), और __delete__() (स्रोत).
Descriptors के संदर्भ में binding behavior का अर्थ है कि किसी दिए गए वेरिएबल, ऑब्जेक्ट या डेटा सेट के लिए मान कैसे सेट किया जा सकता है, क्वेरी (get) किया जा सकता है, या हटाया जा सकता है—इस पूरे इंटरैक्शन को उसी डेटा के टुकड़े से बाइंड किया जाता है, क्योंकि यह केवल उसी डेटा पर लागू होता है जिस पर आपने इसे सेट किया है।
Descriptors को आगे डेटा और नॉन-डेटा descriptors में वर्गीकृत किया जा सकता है। यदि आपके द्वारा लिखा गया descriptor केवल __get__() मेथड रखता है, तो यह नॉन-डेटा descriptor है; दूसरी ओर, __set__() और __delete__() मेथड्स वाली इम्प्लीमेंटेशन को डेटा descriptor कहा जाता है। नॉन-डेटा descriptors केवल रीडेबल होते हैं जबकि डेटा descriptors दोनों रीडेबल और राइटेबल होते हैं।
यह ध्यान देने योग्य है कि descriptors किसी कक्षा (class) को असाइन किए जाते हैं, न कि कक्षा के instance को। कक्षा को संशोधित करने से descriptor स्वयं ओवरराइट या डिलीट होता है, न कि उसके कोड को ट्रिगर करता है (IBM Developer).
अंत में, descriptor कक्षा केवल इन तीन मेथड्स तक सीमित नहीं है, यानी इसमें get, set, और delete मेथड के अलावा अन्य कोई भी एट्रिब्यूट हो सकते हैं।
आइए IBM Developer पेज से प्रेरित होकर get, set, और delete मेथड्स को थोड़ी और विस्तार से समझें:
self वह descriptor instance है जो आप बनाते हैं (Real Python).
object वह ऑब्जेक्ट instance है जिससे आपका descriptor जुड़ा है (Real Python).
type उस ऑब्जेक्ट का प्रकार है जिससे descriptor जुड़ा है (Real Python).
value वह मान है जो descriptor के एट्रिब्यूट को असाइन किया जाता है। get(self, object, type) set(self, object, value) delete(self, object)
__get__() एट्रिब्यूट तक पहुँचता है या जब आप कुछ जानकारी निकालना चाहते हैं तब चलता है। यह एट्रिब्यूट का मान लौटाता है या यदि अनुरोधित एट्रिब्यूट मौजूद नहीं है तो AttributeError अपवाद उठाता है।
__set__() किसी एट्रिब्यूट असाइनमेंट ऑपरेशन में कॉल होता है जो एट्रिब्यूट का मान सेट करता है। यह कुछ नहीं लौटाता, लेकिन AttributeError अपवाद उठा सकता है।
__delete__() डिलीट ऑपरेशन को नियंत्रित करता है, यानी जब आप किसी ऑब्जेक्ट से एट्रिब्यूट हटाना चाहते हैं। यह भी कुछ नहीं लौटाता।
अब descriptors के उद्देश्य को कुछ उदाहरणों की मदद से समझते हैं!
Descriptors का उद्देश्य
आइए एक कक्षा car परिभाषित करें जिसके तीन एट्रिब्यूट हैं: make, model, और fuel_cap। आप कक्षा के एट्रिब्यूट्स को इनिशियलाइज़ करने के लिए __init__() मेथड का उपयोग करेंगे। फिर आप मैजिक फ़ंक्शन __str__() का उपयोग करेंगे, जो बस उन तीन एट्रिब्यूट्स का आउटपुट लौटाएगा जिन्हें आप ऑब्जेक्ट बनाते समय कक्षा को पास करेंगे।
ध्यान दें कि __str__() मेथड ऑब्जेक्ट का स्ट्रिंग प्रतिनिधित्व लौटाता है। इसे तब कॉल किया जाता है जब कक्षा के ऑब्जेक्ट पर print() या str() फ़ंक्शन चलाया जाता है।
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
BMW model X7with a fuel capacity of-40 ltr.संचालित द्वारा
रुकिए, कुछ गड़बड़ है, है न? कार की फ्यूल कैपेसिटी कभी नकारात्मक नहीं हो सकती। फिर भी Python इसे बिना किसी त्रुटि के इनपुट के रूप में स्वीकार कर लेता है। ऐसा इसलिए क्योंकि Python एक डायनेमिक प्रोग्रामिंग भाषा है जो स्पष्ट रूप से टाइप-चेकिंग को सपोर्ट नहीं करती।
इस समस्या से बचने के लिए, __init__() मेथड में एक if कंडीशन जोड़ते हैं और जाँचते हैं कि दी गई फ्यूल कैपेसिटी वैध है या अवैध। यदि दर्ज की गई फ्यूल कैपेसिटी अवैध है, तो ValueError अपवाद उठाएँ।
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-22-1c3d23be72f7>in<module>---->1 car2 =Car("BMW","X7",-40)2print(car2)<ipython-input-20-1e154783588d>in__init__(self, make, model, fuel_cap)5 self.fuel_cap = fuel_cap
6if self.fuel_cap <0:---->7 raise ValueError("Fuel Capacity can never be less than zero")89 def __str__(self):ValueError: Fuel Capacity can never be less than zero
संचालित द्वारा
ऊपर के आउटपुट से आप देख सकते हैं कि अभी के लिए सब ठीक काम कर रहा है क्योंकि यदि फ्यूल कैपेसिटी शून्य से कम है तो प्रोग्राम ValueError उठाता है।
हालाँकि, एक और समस्या हो सकती है, यानी यदि दर्ज की गई fuel capacity एक फ्लोट वैल्यू या स्ट्रिंग हो। केवल फ्यूल कैपेसिटी ही नहीं, कार का make और model भी इन्टीजर वैल्यू हो सकता है। इन सभी मामलों में, प्रोग्राम अपवाद उठाने में विफल रहेगा।
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
-40 model X7with a fuel capacity of40 ltr.संचालित द्वारा
ऊपर के मामले को संभालने के लिए, आप एक और if कंडीशन जोड़ने या टाइप-चेकिंग के लिए isinstance मेथड का उपयोग करने के बारे में सोच सकते हैं।
इस बार त्रुटि को संभालने के लिए बिल्ट-इन मेथड isinstance का उपयोग करते हैं।
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
ifisinstance(self.make,str):print(self.make)else:raise ValueError("Make of the car can never be an integer")if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-34-75b08cba454f>in<module>---->1 car2 =Car(-40,"X7",40)2print(car2)<ipython-input-31-175690bf3b98>in__init__(self, make, model, fuel_cap)7print(self.make)8else:---->9 raise ValueError("Make of the car can never be an integer")1011if self.fuel_cap <0:ValueError: Make of the car can never be an integer
संचालित द्वारा
बहुत बढ़िया! आप इस त्रुटि को भी संभाल पाए।
लेकिन, यदि आप बाद में फ्यूल कैपेसिटी एट्रिब्यूट को स्पष्ट रूप से नकारात्मक 40 में बदलना चाहें तो? इस स्थिति में यह काम नहीं करेगा, क्योंकि टाइप-चेकिंग केवल एक बार __init__() मेथड में होगी। जैसा कि आप जानते हैं, __init__() मेथड एक कंस्ट्रक्टर है, और यह केवल एक बार तब कॉल होता है जब आप कक्षा का ऑब्जेक्ट बनाते हैं। इसलिए, कस्टम टाइप-चेकिंग बाद में विफल हो जाएगी।
इसे एक उदाहरण से समझते हैं।
classCar:def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
ifisinstance(self.make,str):print(self.make)else:raise ValueError("Make of the car can never be an integer")if self.fuel_cap <0:raise ValueError("Fuel Capacity can never be less than zero")def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
-40 model X7with a fuel capacity of40 ltr.संचालित द्वारा
और हो गया! आप टाइप-चेकिंग से बाहर निकलने में सफल हो गए।
अब ऐसे सोचिए, यदि कार के कई और एट्रिब्यूट—जैसे माइलेज, कीमत, एक्सेसरीज़ आदि—भी हों जिन्हें टाइप-चेकिंग की ज़रूरत है और आप यह भी चाहते हैं कि इनमें से कुछ एट्रिब्यूट्स पर केवल read एक्सेस हो। क्या यह परेशान करने वाला नहीं होगा?
ऊपर बताई गई सभी समस्याओं के समाधान के लिए, Python के पास Descriptors हैं जो मदद करते हैं!
जैसा कि आपने ऊपर सीखा, कोई भी कक्षा जो किसी descriptor प्रोटोकॉल के ऑब्जेक्ट के लिए __get__(), __set()__, या __delete()__ मैजिक मेथड्स लागू करती है, उसे Descriptors कहा जाता है। वे यह अतिरिक्त नियंत्रण भी देते हैं कि किसी एट्रिब्यूट को कैसे काम करना चाहिए—जैसे क्या उसे read या write एक्सेस होना चाहिए।
अब ऊपर दिए उदाहरण को Python Descriptor मेथड्स जोड़कर बढ़ाते हैं।
classDescriptor:def__init__(self):
self.__fuel_cap =0def__get__(self, instance, owner):return self.__fuel_cap
def__set__(self, instance, value):ifisinstance(value,int):print(value)else:raise TypeError("Fuel Capacity can only be an integer")if value <0:raise ValueError("Fuel Capacity can never be less than zero")
self.__fuel_cap = value
def__delete__(self, instance):del self.__fuel_cap
संचालित द्वारा
classCar:
fuel_cap = Descriptor()def__init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def__str__(self):return"{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)संचालित द्वारा
40BMW model X7with a fuel capacity of40 ltr.संचालित द्वारा
यदि Descriptor कक्षा आपको जटिल लग रही है तो चिंता न करें। इसे छोटे-छोटे हिस्सों में तोड़कर समझते हैं कि प्रत्येक मेथड असल में क्या कर रहा है।
Descriptor कक्षा के __init__() मेथड में एक लोकल वेरिएबल __fuel_cap को शून्य पर सेट किया गया है। शुरुआत में dunder या डबल अंडरस्कोर का अर्थ है कि वेरिएबल प्राइवेट है। dunder लगाने का उद्देश्य Descriptor कक्षा के फ्यूल कैपेसिटी एट्रिब्यूट को Car कक्षा से अलग पहचानना है।
जैसा कि अब आप जानते हैं, __get__() मेथड एट्रिब्यूट को रिट्रीव करने के लिए उपयोग होता है और यह फ्यूल कैपेसिटी वेरिएबल लौटाता है। यह तीन आर्ग्यूमेंट लेता है—descriptor ऑब्जेक्ट, उस कक्षा के instance का संदर्भ जिसमें descriptor ऑब्जेक्ट instance है, यानी car2, और अंत में owner, जो उस कक्षा का संदर्भ है जिससे instance संबंधित है, यानी Car कक्षा। इस मेथड में, आप बस value एट्रिब्यूट यानी fuel_cap लौटाते हैं, जिसका मान __set__() मेथड में सेट होता है।
__set__() मेथड तब चालू होता है जब एट्रिब्यूट को कोई मान असाइन किया जाता है, और __get__() मेथड के विपरीत, यह कुछ भी वापस नहीं लौटाता। descriptor ऑब्जेक्ट के अलावा इसमें दो आर्ग्यूमेंट होते हैं—instance, जो __get__() जैसा ही है, और value, जो आप एट्रिब्यूट को असाइन करते हैं। इस मेथड में, आप जाँचते हैं कि fuel_cap एट्रिब्यूट को असाइन किया जाने वाला मान इन्टीजर है या नहीं। यदि नहीं, तो आप TypeError अपवाद उठाते हैं। फिर, इसी मेथड में आप यह भी जाँचते हैं कि मान शून्य से कम तो नहीं; यदि हाँ, तो आप एक और अपवाद उठाते हैं, इस बार ValueError। त्रुटियों की जाँच के बाद, आप fuel_cap एट्रिब्यूट को दिए गए मान के बराबर अपडेट करते हैं।
अंत में, __delete__() मेथड तब कॉल होता है जब किसी ऑब्जेक्ट से एट्रिब्यूट हटाया जाता है और यह भी __set__() मेथड की तरह कुछ नहीं लौटाता।
Car कक्षा पहले जैसी ही रहती है। हालाँकि, केवल एक बदलाव यह है कि आप Descriptor() कक्षा का instance fuel_cap के रूप में जोड़ते हैं। ध्यान दें, जैसा कि पहले बताया गया था, descriptor कक्षा का instance किसी कक्षा में class attribute के रूप में जोड़ा जाना चाहिए, न कि instance attribute के रूप में।
जैसे ही आप __init__() मेथड में लोकल वेरिएबल fuel_cap को instance fuel_cap पर सेट करते हैं, यह Descriptor कक्षा के __set__() मेथड को इनवोक करता है।
अब फ्यूल कैपेसिटी को नकारात्मक मान में बदलते हैं और देखते हैं कि प्रोग्राम ValueError अपवाद उठाता है या नहीं।
-40----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-115-1c3d23be72f7>in<module>---->1 car2 =Car("BMW","X7",-40)2print(car2)<ipython-input-107-3e1f3e97d3c7>in__init__(self, make, model, fuel_cap)4 self.make = make
5 self.model = model
---->6 self.fuel_cap = fuel_cap
78 def __str__(self):<ipython-input-106-0b252695aeed>in__set__(self, instance, value)1112if value <0:--->13 raise ValueError("Fuel Capacity can never be less than zero")1415 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
संचालित द्वारा
यदि आपको यहाँ याद हो, तो टाइप-चेकिंग तब असफल हो गई थी जब आपने बाद में एट्रिब्यूट का मान नकारात्मक संख्या में बदल दिया था, क्योंकि टाइप-चेकिंग केवल एक बार __init__() मेथड में लागू थी। अब fuel_cap का मान स्ट्रिंग में अपडेट करते हैं और देखते हैं कि क्या यह त्रुटि देता है।
-1----------------------------------------ValueErrorTraceback(most recent call last)<ipython-input-120-dea9dbe96ebe>in<module>---->1 car2.fuel_cap =-1<ipython-input-106-0b252695aeed>in__set__(self, instance, value)1112if value <0:--->13 raise ValueError("Fuel Capacity can never be less than zero")1415 self.__fuel_cap = value
ValueError: Fuel Capacity can never be less than zero
संचालित द्वारा
----------------------------------------TypeErrorTraceback(most recent call last)<ipython-input-121-0b316a9872c6>in<module>---->1 car2.fuel_cap ="BMW"<ipython-input-106-0b252695aeed>in__set__(self, instance, value)8print(value)9else:--->10 raise TypeError("Fuel Capacity can only be an integer")1112if value <0:TypeError: Fuel Capacity can only be an integer
संचालित द्वारा
बिल्कुल सही! जैसा कि आप देख सकते हैं, बाद में फ्यूल कैपेसिटी एट्रिब्यूट अपडेट करने पर भी यह काम करता है।
Descriptors में एक हल्की समस्या है: जब आप कक्षा का एक नया या दूसरा instance बनाते हैं, तो पिछले instance का मान ओवरराइड हो जाता है। कारण यह है कि descriptors कक्षा से जुड़े होते हैं, instance से नहीं।
इसे नीचे दिए उदाहरण से समझते हैं।
car3 = Car("BMW","X7",48)#created a new instance 'car3' with different valuesसंचालित द्वारा
BMW model X7with a fuel capacity of48 ltr.संचालित द्वारा
निष्कर्ष
ट्यूटोरियल पूरा करने पर बधाई।
यह ट्यूटोरियल उनके लिए था जो Python में सहज हैं और उन्नत स्तर में महारत हासिल करना चाहते हैं। एक अच्छे अभ्यास के रूप में, आप यह जानने की कोशिश कर सकते हैं कि आज के ट्यूटोरियल में चर्चित instance overriding की समस्या को कैसे सुलझाया जा सकता है।
कृपया इस ट्यूटोरियल से संबंधित कोई भी प्रश्न नीचे टिप्पणी अनुभाग में पूछें।
यदि आप Python के बारे में और सीखना चाहते हैं, तो DataCamp का पूर्ण Python Programming स्किल ट्रैक देखें।
Python के साथ डेटा विश्लेषण की बुनियादी बातें सिर्फ चार घंटे में सीखें। यह ऑनलाइन पाठ्यक्रम Python इंटरफ़ेस का परिचय देगा और लोकप्रिय पैकेजों का अन्वेषण करेगा।
Python descriptors, या सामान्य रूप से कहें तो descriptors, आपको ऐसा शक्तिशाली तरीका देते हैं जिससे आप पुन: प्रयोज्य कोड लिख सकें जो कक्षाओं (classes) के बीच साझा किया जा सके। वे इनहेरिटेंस की अवधारणा जैसे लग सकते हैं, लेकिन तकनीकी रूप से वे अलग हैं। ये गुण (attribute) एक्सेस को इंटरसेप्ट करने का एक सामान्य-उद्देश्य तरीका हैं। प्रॉपर्टीज, स्टैटिक मेथड्स, क्लास मेथड्स, सुपर मेथड्स आदि के पीछे descriptors ही मैकेनिज़्म है।
Descriptors को Python संस्करण 2.2 में जोड़ा गया था, और तब से इन्हें जादुई चीज़ों के रूप में माना गया है जिन्होंने पारंपरिक कक्षाओं को एक नया स्टाइल दिया। ये ऐसी कक्षाएँ हैं जो आपको किसी दूसरी कक्षा में प्रबंधित प्रॉपर्टीज (managed properties) लागू करने देती हैं। विशेष रूप से, वे
__get__(),__set__()और__delete__()मेथड के लिए इंटरफेस लागू करती हैं, जो कई कारणों से उन्हें रोचक बनाते हैं। उदाहरण के लिए, Python में आपने पहले जो class decorator और property decorator देखे होंगे।सरल शब्दों में, कोई भी कक्षा जो किसी ऑब्जेक्ट के लिए
__get__(),__set()__, या__delete()__मेथड लागू करती है, उसे "Descriptor" कहा जाता है। सीधे Python के आधिकारिक प्रलेखन से उद्धृत करें तो, एक descriptor वह ऑब्जेक्ट एट्रिब्यूट है जिसमेंbinding behaviorहोता है, यानी जिसका एट्रिब्यूट एक्सेस descriptor प्रोटोकॉल के मेथड्स द्वारा ओवरराइड किया गया हो। ये मेथड्स हैं__get__(),__set__(), और__delete__()(स्रोत).Descriptors के संदर्भ में
binding behaviorका अर्थ है कि किसी दिए गए वेरिएबल, ऑब्जेक्ट या डेटा सेट के लिए मान कैसे सेट किया जा सकता है, क्वेरी (get) किया जा सकता है, या हटाया जा सकता है—इस पूरे इंटरैक्शन को उसी डेटा के टुकड़े सेबाइंडकिया जाता है, क्योंकि यह केवल उसी डेटा पर लागू होता है जिस पर आपने इसे सेट किया है।Descriptors को आगे डेटा और नॉन-डेटा descriptors में वर्गीकृत किया जा सकता है। यदि आपके द्वारा लिखा गया descriptor केवल
__get__()मेथड रखता है, तो यह नॉन-डेटा descriptor है; दूसरी ओर,__set__()और__delete__()मेथड्स वाली इम्प्लीमेंटेशन को डेटा descriptor कहा जाता है। नॉन-डेटा descriptors केवल रीडेबल होते हैं जबकि डेटा descriptors दोनों रीडेबल और राइटेबल होते हैं।यह ध्यान देने योग्य है कि descriptors किसी कक्षा (class) को असाइन किए जाते हैं, न कि कक्षा के instance को। कक्षा को संशोधित करने से descriptor स्वयं ओवरराइट या डिलीट होता है, न कि उसके कोड को ट्रिगर करता है (IBM Developer).
अंत में, descriptor कक्षा केवल इन तीन मेथड्स तक सीमित नहीं है, यानी इसमें
get,set, औरdeleteमेथड के अलावा अन्य कोई भी एट्रिब्यूट हो सकते हैं।आइए IBM Developer पेज से प्रेरित होकर
get,set, औरdeleteमेथड्स को थोड़ी और विस्तार से समझें:selfवह descriptor instance है जो आप बनाते हैं (Real Python).objectवह ऑब्जेक्ट instance है जिससे आपका descriptor जुड़ा है (Real Python).typeउस ऑब्जेक्ट का प्रकार है जिससे descriptor जुड़ा है (Real Python).valueवह मान है जो descriptor के एट्रिब्यूट को असाइन किया जाता है। get(self, object, type) set(self, object, value) delete(self, object)__get__()एट्रिब्यूट तक पहुँचता है या जब आप कुछ जानकारी निकालना चाहते हैं तब चलता है। यह एट्रिब्यूट का मान लौटाता है या यदि अनुरोधित एट्रिब्यूट मौजूद नहीं है तो AttributeError अपवाद उठाता है।__set__()किसी एट्रिब्यूट असाइनमेंट ऑपरेशन में कॉल होता है जो एट्रिब्यूट का मान सेट करता है। यह कुछ नहीं लौटाता, लेकिन AttributeError अपवाद उठा सकता है।__delete__()डिलीट ऑपरेशन को नियंत्रित करता है, यानी जब आप किसी ऑब्जेक्ट से एट्रिब्यूट हटाना चाहते हैं। यह भी कुछ नहीं लौटाता।अब descriptors के उद्देश्य को कुछ उदाहरणों की मदद से समझते हैं!
Descriptors का उद्देश्य
आइए एक कक्षा
carपरिभाषित करें जिसके तीन एट्रिब्यूट हैं:make,model, औरfuel_cap। आप कक्षा के एट्रिब्यूट्स को इनिशियलाइज़ करने के लिए__init__()मेथड का उपयोग करेंगे। फिर आप मैजिक फ़ंक्शन__str__()का उपयोग करेंगे, जो बस उन तीन एट्रिब्यूट्स का आउटपुट लौटाएगा जिन्हें आप ऑब्जेक्ट बनाते समय कक्षा को पास करेंगे।ध्यान दें कि
__str__()मेथड ऑब्जेक्ट का स्ट्रिंग प्रतिनिधित्व लौटाता है। इसे तब कॉल किया जाता है जब कक्षा के ऑब्जेक्ट परprint()याstr()फ़ंक्शन चलाया जाता है।तो जैसा कि आप ऊपर के आउटपुट से देख सकते हैं, सब कुछ बढ़िया है!
अब कार की फ्यूल कैपेसिटी को नकारात्मक 40 में बदलते हैं।
रुकिए, कुछ गड़बड़ है, है न? कार की फ्यूल कैपेसिटी कभी नकारात्मक नहीं हो सकती। फिर भी Python इसे बिना किसी त्रुटि के इनपुट के रूप में स्वीकार कर लेता है। ऐसा इसलिए क्योंकि Python एक डायनेमिक प्रोग्रामिंग भाषा है जो स्पष्ट रूप से टाइप-चेकिंग को सपोर्ट नहीं करती।
इस समस्या से बचने के लिए,
__init__()मेथड में एकifकंडीशन जोड़ते हैं और जाँचते हैं कि दी गई फ्यूल कैपेसिटी वैध है या अवैध। यदि दर्ज की गई फ्यूल कैपेसिटी अवैध है, तो ValueError अपवाद उठाएँ।ऊपर के आउटपुट से आप देख सकते हैं कि अभी के लिए सब ठीक काम कर रहा है क्योंकि यदि फ्यूल कैपेसिटी शून्य से कम है तो प्रोग्राम
ValueErrorउठाता है।हालाँकि, एक और समस्या हो सकती है, यानी यदि दर्ज की गई
fuel capacityएक फ्लोट वैल्यू या स्ट्रिंग हो। केवल फ्यूल कैपेसिटी ही नहीं, कार काmakeऔरmodelभी इन्टीजर वैल्यू हो सकता है। इन सभी मामलों में, प्रोग्राम अपवाद उठाने में विफल रहेगा।ऊपर के मामले को संभालने के लिए, आप एक और
ifकंडीशन जोड़ने या टाइप-चेकिंग के लिएisinstanceमेथड का उपयोग करने के बारे में सोच सकते हैं।इस बार त्रुटि को संभालने के लिए बिल्ट-इन मेथड
isinstanceका उपयोग करते हैं।बहुत बढ़िया! आप इस त्रुटि को भी संभाल पाए।
लेकिन, यदि आप बाद में फ्यूल कैपेसिटी एट्रिब्यूट को स्पष्ट रूप से नकारात्मक 40 में बदलना चाहें तो? इस स्थिति में यह काम नहीं करेगा, क्योंकि टाइप-चेकिंग केवल एक बार
__init__()मेथड में होगी। जैसा कि आप जानते हैं,__init__()मेथड एक कंस्ट्रक्टर है, और यह केवल एक बार तब कॉल होता है जब आप कक्षा का ऑब्जेक्ट बनाते हैं। इसलिए, कस्टम टाइप-चेकिंग बाद में विफल हो जाएगी।इसे एक उदाहरण से समझते हैं।
और हो गया! आप टाइप-चेकिंग से बाहर निकलने में सफल हो गए।
अब ऐसे सोचिए, यदि कार के कई और एट्रिब्यूट—जैसे माइलेज, कीमत, एक्सेसरीज़ आदि—भी हों जिन्हें टाइप-चेकिंग की ज़रूरत है और आप यह भी चाहते हैं कि इनमें से कुछ एट्रिब्यूट्स पर केवल
readएक्सेस हो। क्या यह परेशान करने वाला नहीं होगा?ऊपर बताई गई सभी समस्याओं के समाधान के लिए, Python के पास Descriptors हैं जो मदद करते हैं!
जैसा कि आपने ऊपर सीखा, कोई भी कक्षा जो किसी descriptor प्रोटोकॉल के ऑब्जेक्ट के लिए
__get__(),__set()__, या__delete()__मैजिक मेथड्स लागू करती है, उसे Descriptors कहा जाता है। वे यह अतिरिक्त नियंत्रण भी देते हैं कि किसी एट्रिब्यूट को कैसे काम करना चाहिए—जैसे क्या उसे read या write एक्सेस होना चाहिए।अब ऊपर दिए उदाहरण को Python Descriptor मेथड्स जोड़कर बढ़ाते हैं।
यदि Descriptor कक्षा आपको जटिल लग रही है तो चिंता न करें। इसे छोटे-छोटे हिस्सों में तोड़कर समझते हैं कि प्रत्येक मेथड असल में क्या कर रहा है।
__init__()मेथड में एक लोकल वेरिएबल__fuel_capको शून्य पर सेट किया गया है। शुरुआत मेंdunderया डबल अंडरस्कोर का अर्थ है कि वेरिएबल प्राइवेट है। dunder लगाने का उद्देश्य Descriptor कक्षा के फ्यूल कैपेसिटी एट्रिब्यूट को Car कक्षा से अलग पहचानना है।__get__()मेथड एट्रिब्यूट को रिट्रीव करने के लिए उपयोग होता है और यह फ्यूल कैपेसिटी वेरिएबल लौटाता है। यह तीन आर्ग्यूमेंट लेता है—descriptor ऑब्जेक्ट, उस कक्षा के instance का संदर्भ जिसमें descriptor ऑब्जेक्ट instance है, यानीcar2, और अंत में owner, जो उस कक्षा का संदर्भ है जिससे instance संबंधित है, यानीCarकक्षा। इस मेथड में, आप बसvalueएट्रिब्यूट यानीfuel_capलौटाते हैं, जिसका मान__set__()मेथड में सेट होता है।__set__()मेथड तब चालू होता है जब एट्रिब्यूट को कोई मान असाइन किया जाता है, और__get__()मेथड के विपरीत, यह कुछ भी वापस नहीं लौटाता। descriptor ऑब्जेक्ट के अलावा इसमें दो आर्ग्यूमेंट होते हैं—instance, जो__get__()जैसा ही है, और value, जो आप एट्रिब्यूट को असाइन करते हैं। इस मेथड में, आप जाँचते हैं किfuel_capएट्रिब्यूट को असाइन किया जाने वाला मान इन्टीजर है या नहीं। यदि नहीं, तो आपTypeErrorअपवाद उठाते हैं। फिर, इसी मेथड में आप यह भी जाँचते हैं कि मान शून्य से कम तो नहीं; यदि हाँ, तो आप एक और अपवाद उठाते हैं, इस बारValueError। त्रुटियों की जाँच के बाद, आपfuel_capएट्रिब्यूट को दिए गए मान के बराबर अपडेट करते हैं।__delete__()मेथड तब कॉल होता है जब किसी ऑब्जेक्ट से एट्रिब्यूट हटाया जाता है और यह भी__set__()मेथड की तरह कुछ नहीं लौटाता।Carकक्षा पहले जैसी ही रहती है। हालाँकि, केवल एक बदलाव यह है कि आपDescriptor()कक्षा का instancefuel_capके रूप में जोड़ते हैं। ध्यान दें, जैसा कि पहले बताया गया था, descriptor कक्षा का instance किसी कक्षा में class attribute के रूप में जोड़ा जाना चाहिए, न कि instance attribute के रूप में।जैसे ही आप
__init__()मेथड में लोकल वेरिएबलfuel_capको instancefuel_capपर सेट करते हैं, यह Descriptor कक्षा के__set__()मेथड को इनवोक करता है।अब फ्यूल कैपेसिटी को नकारात्मक मान में बदलते हैं और देखते हैं कि प्रोग्राम ValueError अपवाद उठाता है या नहीं।
यदि आपको यहाँ याद हो, तो टाइप-चेकिंग तब असफल हो गई थी जब आपने बाद में एट्रिब्यूट का मान नकारात्मक संख्या में बदल दिया था, क्योंकि टाइप-चेकिंग केवल एक बार
__init__()मेथड में लागू थी। अबfuel_capका मान स्ट्रिंग में अपडेट करते हैं और देखते हैं कि क्या यह त्रुटि देता है।बिल्कुल सही! जैसा कि आप देख सकते हैं, बाद में फ्यूल कैपेसिटी एट्रिब्यूट अपडेट करने पर भी यह काम करता है।
Descriptors में एक हल्की समस्या है: जब आप कक्षा का एक नया या दूसरा instance बनाते हैं, तो पिछले instance का मान ओवरराइड हो जाता है। कारण यह है कि descriptors कक्षा से जुड़े होते हैं, instance से नहीं।
इसे नीचे दिए उदाहरण से समझते हैं।
जब आप instance
car2को प्रिंट करेंगे, तो आप देखेंगे कि मानcar3द्वारा ओवरराइड हो गए हैं।निष्कर्ष
ट्यूटोरियल पूरा करने पर बधाई।
यह ट्यूटोरियल उनके लिए था जो Python में सहज हैं और उन्नत स्तर में महारत हासिल करना चाहते हैं। एक अच्छे अभ्यास के रूप में, आप यह जानने की कोशिश कर सकते हैं कि आज के ट्यूटोरियल में चर्चित instance overriding की समस्या को कैसे सुलझाया जा सकता है।
कृपया इस ट्यूटोरियल से संबंधित कोई भी प्रश्न नीचे टिप्पणी अनुभाग में पूछें।
यदि आप Python के बारे में और सीखना चाहते हैं, तो DataCamp का पूर्ण Python Programming स्किल ट्रैक देखें।
हमारा Python Functions Tutorial देखें।