__Get__ और __set__ और पायथन विवरणकों को समझना


310

मैं समझने की कोशिश कर रहा हूं कि पायथन के वर्णनकर्ता क्या हैं और वे किसके लिए उपयोगी हैं। मैं समझता हूं कि वे कैसे काम करते हैं, लेकिन यहां मेरे संदेह हैं। निम्नलिखित कोड पर विचार करें:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)


class Temperature(object):
    celsius = Celsius()
  1. मुझे डिस्क्रिप्टर क्लास की आवश्यकता क्यों है?

  2. क्या है instanceऔर ownerयहाँ? (में __get__)। इन मापदंडों का उद्देश्य क्या है?

  3. मैं इस उदाहरण को कैसे कॉल / उपयोग करूंगा?

जवाबों:


147

वर्णनकर्ता है कि पायथन का propertyप्रकार कैसे लागू किया जाता है। एक विवरणक केवल लागू करता है __get__, __set__आदि और फिर इसकी परिभाषा में एक और वर्ग में जोड़ा जाता है (जैसा कि आपने तापमान वर्ग के साथ ऊपर किया था)। उदाहरण के लिए:

temp=Temperature()
temp.celsius #calls celsius.__get__

उस संपत्ति तक पहुंच जिसे आपने डिस्क्रिप्टर को सौंपा है ( celsiusउपरोक्त उदाहरण में) उपयुक्त डिस्क्रिप्टर विधि को कॉल करता है।

instanceमें __get__वर्ग का उदाहरण है (इसलिए ऊपर, __get__प्राप्त होगा temp, जबकि ownerवर्णक के साथ वर्ग है (इसलिए यह होगा Temperature)।

आपको उस तर्क को अतिक्रमण करने के लिए एक वर्णक वर्ग का उपयोग करने की आवश्यकता है जो इसे शक्ति प्रदान करता है। इस तरह, यदि डिस्क्रिप्टर का उपयोग कुछ महंगे ऑपरेशन (उदाहरण के लिए) को कैश करने के लिए किया जाता है, तो यह अपने आप मूल्य को स्टोर कर सकता है न कि उसकी क्लास को।

विवरणकर्ताओं के बारे में एक लेख यहां पाया जा सकता है

संपादित करें: जैसा कि jchl ने टिप्पणी में बताया है, यदि आप बस कोशिश करते हैं Temperature.celsius, तो instanceहोगा None


6
बीच क्या अंतर है selfऔर instance?
लेम्मा प्रिज्म

2
'उदाहरण' किसी भी वर्ग का उदाहरण हो सकता है, स्व उसी वर्ग का उदाहरण होगा।
बिगिनर

3
@LemmaPrism selfडिस्क्रिप्टर का उदाहरण है, instanceवर्ग का उदाहरण है (यदि तुरंत) डिस्क्रिप्टर में ( instance.__class__ is owner) है।
22

Temperature.celsius0.0कोड के अनुसार मूल्य देता है celsius = Celsius()। डिस्क्रिप्टर सेल्सियस कहा जाता है, इसलिए इसकी आवृत्ति 0.0तापमान वर्ग विशेषता, तापमान को सौंपा init मूल्य है ।
एंजल सालाजार

109

मुझे डिस्क्रिप्टर क्लास की आवश्यकता क्यों है?

यह आपको अतिरिक्त नियंत्रण देता है कि कैसे विशेषताएँ काम करती हैं। उदाहरण के लिए, यदि आप जावा में गेटर्स और सेटर का उपयोग करते हैं, तो यह पायथन का तरीका है। एक फायदा यह है कि यह उपयोगकर्ताओं को एक विशेषता की तरह दिखता है (इसमें सिंटैक्स में कोई बदलाव नहीं है)। तो आप एक साधारण विशेषता के साथ शुरू कर सकते हैं और फिर, जब आपको कुछ फैंसी करने की आवश्यकता होती है, तो एक विवरणक पर स्विच करें।

एक विशेषता सिर्फ एक परिवर्तनशील मूल्य है। एक डिस्क्रिप्टर आपको एक मान पढ़ने या स्थापित करने (या हटाने) के दौरान मनमाने कोड को निष्पादित करने देता है। तो आप एक डेटाबेस में एक क्षेत्र के लिए एक विशेषता को मैप करने के लिए इसका उपयोग करने की कल्पना कर सकते हैं, उदाहरण के लिए - एक प्रकार का ओआरएम।

एक अन्य उपयोग एक अपवाद को फेंककर एक नए मूल्य को स्वीकार करने से इंकार कर सकता है __set__- प्रभावी रूप से केवल "विशेषता" बना रहा है।

क्या है instanceऔर ownerयहाँ? (में __get__)। इन मापदंडों का उद्देश्य क्या है?

यह बहुत ही सूक्ष्म है (और इसका कारण मैं यहां एक नया उत्तर लिख रहा हूं - मैंने इस प्रश्न को एक ही बात पर आश्चर्य करते हुए पाया और मौजूदा उत्तर को बहुत अच्छा नहीं पाया)।

डिस्क्रिप्टर को एक वर्ग पर परिभाषित किया गया है, लेकिन आमतौर पर इसे एक उदाहरण से कहा जाता है। यह एक उदाहरण दोनों से कहा जाता है जब है instanceऔर ownerस्थापित कर रहे हैं (और आप काम कर सकते हैं ownerसे instanceतो यह थोड़े व्यर्थ लगता है)। लेकिन जब एक वर्ग से बुलाया जाता है, तो केवल ownerसेट किया जाता है - यही कारण है कि यह वहां है।

यह केवल इसके लिए आवश्यक है __get__क्योंकि यह केवल एक ही है जिसे कक्षा में बुलाया जा सकता है। यदि आप वर्ग मान सेट करते हैं तो आप डिस्क्रिप्टर को स्वयं सेट करते हैं। इसी प्रकार विलोपन के लिए। यही कारण है कि ownerवहाँ की जरूरत नहीं है।

मैं इस उदाहरण को कैसे कॉल / उपयोग करूंगा?

ठीक है, यहाँ समान वर्गों का उपयोग करके एक शांत चाल है:

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5


class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f


t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(मैं अजगर 3 का उपयोग कर रहा हूं; अजगर 2 के लिए आपको यह सुनिश्चित करने की आवश्यकता है कि वे विभाजन हैं / 5.0और / 9.0)। देता है कि:

100.0
32.0

अब अन्य, यकीनन बेहतर तरीके से अजगर में एक ही प्रभाव को प्राप्त करने के तरीके हैं (उदाहरण के लिए अगर सेल्सियस एक संपत्ति थी, जो एक ही बुनियादी तंत्र है लेकिन तापमान स्रोत के अंदर सभी स्रोत रखता है), लेकिन यह दिखाता है कि क्या किया जा सकता है ...


2
रूपांतरण गलत हैं: उन्हें C = 5 (F / 32) / 9, F = 32 + 9C / 5 होना चाहिए।
मुसिफिल

1
सुनिश्चित करें कि आपके पास तापमान की एक वस्तु है। निम्नलिखित सामान गड़बड़ कर रहा है। t1 = तापमान (190) प्रिंट t1.c सेल्सियस t1.c सेल्सियस = 100 प्रिंट t1.fahrenheit अब जब आप t.celcius और t.fahrenheit की जांच करते हैं, तो वे संशोधित भी हो जाते हैं। t.celcius 115 है और t.fahrenheit 32 है। जो स्पष्ट रूप से गलत है। @ ईरिक
ईशान भट्ट

1
@IshanBhatt: मुझे लगता है कि त्रुटि के कारण ऊपर मुसिफिल द्वारा इंगित किया गया है। इसके अलावा, यह मेरा जवाब नहीं है
एरिक

69

मैं यह समझने की कोशिश कर रहा हूं कि पायथन के वर्णनकर्ता क्या हैं और वे किसके लिए उपयोगी हो सकते हैं।

निम्नलिखित विशेष विधियों में से किसी एक के साथ वर्णक वर्ग गुण (जैसे गुण या विधियाँ) हैं:

  • __get__ (गैर-डेटा विवरणक विधि, उदाहरण के लिए किसी विधि / फ़ंक्शन पर)
  • __set__ (डेटा डिस्क्रिप्टर विधि, उदाहरण के लिए एक संपत्ति उदाहरण पर)
  • __delete__ (डेटा विवरणक विधि)

इन डिस्क्रिप्टर ऑब्जेक्ट्स का उपयोग अन्य ऑब्जेक्ट क्लास परिभाषाओं पर विशेषताओं के रूप में किया जा सकता है। (अर्थात्, वे __dict__वर्ग वस्तु में रहते हैं।)

डिस्क्रिप्टिव ऑब्जेक्ट्स का उपयोग प्रोग्रामेटिक रूप से बिंदीदार लुकअप (जैसे foo.descriptor) के परिणाम को सामान्य अभिव्यक्ति, असाइनमेंट और यहां तक ​​कि डिलीशन के प्रबंधन के लिए किया जा सकता है ।

कार्य / तरीकों, बाध्य तरीकों, property, classmethod, और staticmethodसभी उपयोग को नियंत्रित करने के इन विशेष तरीकों कि वे किस तरह बिंदीदार देखने के माध्यम से पहुंचा जा सकता है।

एक डेटा डिस्क्रिप्टर , जैसे property, ऑब्जेक्ट की एक सरल स्थिति के आधार पर विशेषताओं के आलसी मूल्यांकन के लिए अनुमति दे सकता है, यदि आप प्रत्येक संभावित विशेषता को कम से कम मेमोरी का उपयोग करने के उदाहरणों की अनुमति देते हैं।

एक अन्य डेटा डिस्क्रिप्टर, एक member_descriptor, जिसके द्वारा बनाया गया है __slots__, वर्ग को अधिक लचीले लेकिन अंतरिक्ष-उपभोग के बजाय एक उत्परिवर्तित टपल-जैसे डेटास्ट्रक्चर में डेटा को स्टोर करने की अनुमति देकर मेमोरी सेविंग की अनुमति देता है __dict__

गैर-डेटा डिस्क्रिप्टर, आमतौर पर उदाहरण, वर्ग और स्थिर तरीकों से, उनके गैर-डेटा डिस्क्रिप्टर विधि से उनके निहित पहले तर्क (आमतौर पर नाम clsऔर selfक्रमशः) प्राप्त होते हैं __get__

पायथन के अधिकांश उपयोगकर्ताओं को केवल सरल उपयोग सीखने की आवश्यकता है, और आगे वर्णनकर्ताओं के कार्यान्वयन को सीखने या समझने की कोई आवश्यकता नहीं है।

गहराई में: डिस्क्रिप्टर्स क्या हैं?

एक डिस्क्रिप्टर निम्नलिखित विधियों में से किसी के साथ एक वस्तु है ( __get__, __set__या __delete__,), जिसका उद्देश्य डॉटेड-लुकअप के माध्यम से उपयोग किया जाना है जैसे कि यह एक उदाहरण का विशिष्ट गुण है। एक स्वामी-वस्तु के लिए, obj_instanceएक descriptorवस्तु के साथ :

  • obj_instance.descriptorका आह्वान
    descriptor.__get__(self, obj_instance, owner_class)वापस लौटने वाली value
    यह कैसे सभी तरीकों और getएक संपत्ति काम पर।

  • obj_instance.descriptor = valueइनवॉइस
    descriptor.__set__(self, obj_instance, value)लौटना None
    यह setterएक संपत्ति पर काम करता है।

  • del obj_instance.descriptorइनवॉइस
    descriptor.__delete__(self, obj_instance)लौटना None
    यह deleterएक संपत्ति पर काम करता है।

obj_instanceवह उदाहरण है जिसका वर्ग वर्णनात्मक वस्तु का उदाहरण समाहित करता है। वर्णनकर्ताself का उदाहरण है (शायद कक्षा के लिए सिर्फ एक )obj_instance

इसे कोड के साथ परिभाषित करने के लिए, एक ऑब्जेक्ट एक डिस्क्रिप्टर है यदि इसकी विशेषताओं का सेट आवश्यक विशेषताओं में से किसी के साथ प्रतिच्छेद करता है:

def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))

एक डेटा डिस्क्रिप्टर में एक __set__और / या होता है __delete__
एक गैर-डेटा-डिस्क्रिप्टर में न तो है __set__और न ही __delete__

def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))

बिल्टिन डिसिप्लिनर ऑब्जेक्ट उदाहरण:

  • classmethod
  • staticmethod
  • property
  • सामान्य रूप से कार्य करता है

गैर-डेटा डिस्क्रिप्टर्स

हम देख सकते हैं कि classmethodऔर staticmethodगैर-डेटा-डिस्क्रिप्टर्स हैं:

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

दोनों में केवल __get__विधि है:

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

ध्यान दें कि सभी कार्य गैर-डेटा-डिस्क्रिप्टर्स भी हैं:

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

डेटा डिस्क्रिप्टर, property

हालाँकि, propertyएक डेटा-डिस्क्रिप्टर है:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

बिंदीदार लुकअप ऑर्डर

ये महत्वपूर्ण अंतर हैं , क्योंकि वे एक बिंदीदार देखने के लिए लुकअप ऑर्डर को प्रभावित करते हैं।

obj_instance.attribute
  1. सबसे पहले यह देखने के लिए कि क्या विशेषता उदाहरण के वर्ग पर एक डेटा-डिस्क्रिप्टर है,
  2. यदि नहीं, तो यह देखना है कि क्या विशेषता obj_instanceएस में है __dict__, तो
  3. यह अंत में एक गैर-डेटा-डिस्क्रिप्टर पर वापस आ जाता है।

इस लुकअप ऑर्डर का परिणाम यह है कि गैर-डेटा-डिस्क्रिप्टर्स जैसे फ़ंक्शन / तरीके इंस्टेंसेस द्वारा ओवरराइड किए जा सकते हैं ।

रीकैप और नेक्स्ट स्टेप्स

हमने सीखा है कि वर्णनकर्ता से किसी के साथ वस्तुओं रहे हैं __get__, __set__या __delete__। इन डिस्क्रिप्टर ऑब्जेक्ट्स का उपयोग अन्य ऑब्जेक्ट क्लास परिभाषाओं पर विशेषताओं के रूप में किया जा सकता है। अब हम उदाहरण के रूप में आपके कोड का उपयोग करते हुए, उनका उपयोग कैसे करते हैं, इस पर गौर करेंगे।


प्रश्न से कोड का विश्लेषण

यहां आपका कोड है, इसके बाद आपके प्रश्न और प्रत्येक के उत्तर:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()
  1. मुझे डिस्क्रिप्टर क्लास की आवश्यकता क्यों है?

आपका वर्णनकर्ता यह सुनिश्चित करता है कि आपके पास हमेशा इस श्रेणी की विशेषता के लिए एक फ़्लोट है Temperature, और आप delउस विशेषता को हटाने के लिए उपयोग नहीं कर सकते हैं :

>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

अन्यथा, आपके विवरणकर्ता स्वामी के वर्ग-वर्ग और उदाहरणों को अनदेखा करते हैं, इसके बजाय, विवरणक में स्थिति संग्रहीत करते हैं। आप एक सरल वर्ग विशेषता के साथ सभी उदाहरणों में आसानी से राज्य साझा कर सकते हैं (इसलिए जब तक आप इसे हमेशा कक्षा के लिए एक फ़्लोट के रूप में सेट करते हैं और इसे कभी नहीं हटाते हैं, या ऐसा करने वाले आपके कोड के उपयोगकर्ताओं के साथ सहज हैं):

class Temperature(object):
    celsius = 0.0

यह आपके उदाहरण के समान व्यवहार करता है (नीचे दिए गए प्रश्न 3 की प्रतिक्रिया देखें), लेकिन पायथन बिलिन ( property) का उपयोग करता है , और इसे अधिक मुहावरेदार माना जाएगा:

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
  1. यहाँ उदाहरण और मालिक क्या है? ( पाने में )। इन मापदंडों का उद्देश्य क्या है?

instanceवर्णनकर्ता को कॉल करने वाले स्वामी का उदाहरण है। स्वामी वह वर्ग है जिसमें डेटा पॉइंट तक पहुंच का प्रबंधन करने के लिए डिस्क्रिप्टर ऑब्जेक्ट का उपयोग किया जाता है। अधिक वर्णनात्मक चर नामों के लिए इस उत्तर के पहले पैराग्राफ के बगल में वर्णनकर्ताओं को परिभाषित करने वाले विशेष तरीकों के विवरण देखें।

  1. मैं इस उदाहरण को कैसे कॉल / उपयोग करूंगा?

यहाँ एक प्रदर्शन है:

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

आप विशेषता नहीं हटा सकते:

>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

और आप एक चर को निर्दिष्ट नहीं कर सकते हैं जिसे फ्लोट में नहीं बदला जा सकता है:

>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02

अन्यथा, आपके पास यहां जो कुछ भी है वह सभी उदाहरणों के लिए एक वैश्विक स्थिति है, जिसे किसी भी उदाहरण को निर्दिष्ट करके प्रबंधित किया जाता है।

यह उम्मीद है कि सबसे अनुभवी पायथन प्रोग्रामर इस परिणाम को पूरा करेंगे property, डेकोरेटर का उपयोग करना होगा , जो हुड के नीचे एक ही विवरणक का उपयोग करता है, लेकिन व्यवहार को मालिक वर्ग के कार्यान्वयन में लाता है (फिर से, जैसा कि ऊपर परिभाषित किया गया है):

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)

जिसमें कोड के मूल टुकड़े का सटीक अपेक्षित व्यवहार है:

>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02

निष्कर्ष

हमने उन विशेषताओं को कवर किया है जो डिस्क्रिप्टर को परिभाषित करते हैं, डेटा- और गैर-डेटा-डिस्क्रिप्टर के बीच का अंतर, निर्मित वस्तुएं जो उनका उपयोग करती हैं, और उपयोग के बारे में विशिष्ट प्रश्न।

तो फिर, आप प्रश्न के उदाहरण का उपयोग कैसे करेंगे? मुझे आशा है कि आप नहीं करेंगे। मुझे आशा है कि आप मेरे पहले सुझाव (एक साधारण वर्ग विशेषता) के साथ शुरू करेंगे और यदि आपको लगता है कि यह आवश्यक है तो दूसरे सुझाव (संपत्ति सज्जाकार) पर आगे बढ़ेंगे।


1
अच्छा, मैंने इस उत्तर से सबसे अधिक सीखा (निश्चित रूप से दूसरों से भी सीखा)। इस कथन के बारे में एक सवाल "अपेक्षित अनुभव जो कि अधिकांश अनुभवी पायथन प्रोग्रामर इस परिणाम को पूरा करेंगे ..."। विवरण के पहले और बाद में आपके द्वारा परिभाषित टेंपरेचर क्लास समान है। क्या मुझे याद आया कि आप यहाँ क्या कर रहे हैं?
योलो वोए

1
@YoloVoe नहीं, यह सही है, मैंने इस बात पर जोर देने के लिए कुछ पैतृक क्रिया को जोड़ा कि यह ऊपर का दोहराव है।
हारून हॉल

1
यह एक अद्भुत जवाब है। मैं कुछ अधिक से अधिक बार इसे पढ़ने के लिए की आवश्यकता होगी, लेकिन मैं अजगर की मेरी समझ बस कुछ ही नौच को छोड़ की तरह महसूस
लुकास युवा

20

विवरणों के विवरण में जाने से पहले यह जानना महत्वपूर्ण हो सकता है कि पायथन कार्यों में विशेषता की खोज कैसे होती है। यह मानता है कि कक्षा में कोई मेटाक्लस नहीं है और यह डिफ़ॉल्ट कार्यान्वयन का उपयोग करता है __getattribute__(दोनों का उपयोग व्यवहार को "अनुकूलित" करने के लिए किया जा सकता है)।

इस मामले में विशेषता लुकअप (पायथन 3.x में या पायथन 2.x में नई शैली की कक्षाओं के लिए) का सबसे अच्छा चित्रण अंडरस्टैंडिंग पायथन मेटाक्लस (आयनल के कोडेलॉग) से है । छवि :"गैर-अनुकूलन योग्य विशेषता लुकअप" के विकल्प के रूप में उपयोग करती है ।

यह एक foobarपर एक विशेषता की खोज का प्रतिनिधित्व करता instanceहै Class:

यहां छवि विवरण दर्ज करें

दो शर्तें यहां महत्वपूर्ण हैं:

  • यदि वर्ग में instanceविशेषता नाम के लिए एक प्रविष्टि है और यह है __get__और __set__
  • तो instanceहै नहीं यह है विशेषता नाम के लिए प्रवेश लेकिन कक्षा एक है और __get__

यही कारण है कि वर्णनकर्ता इसमें आते हैं:

  • डेटा डिस्क्रिप्टर जिसमें दोनों हैं __get__और __set__
  • गैर-डेटा विवरणक जो केवल हैं __get__

दोनों मामलों में लौटाया गया मूल्य __get__उदाहरण के साथ पहले तर्क के रूप में और दूसरे तर्क के रूप में वर्ग के माध्यम से जाता है।

वर्ग विशेषता लुकअप के लिए लुकअप और भी जटिल है (उदाहरण के लिए देखें क्लास विशेषता लुकअप (उपर्युक्त ब्लॉग में) )।

आइए अपने विशिष्ट प्रश्नों की ओर बढ़ें:

मुझे डिस्क्रिप्टर क्लास की आवश्यकता क्यों है?

ज्यादातर मामलों में आपको डिस्क्रिप्टर क्लासेस लिखने की आवश्यकता नहीं होती है! हालाँकि आप शायद एक बहुत ही नियमित अंत उपयोगकर्ता हैं। उदाहरण के लिए कार्य। फ़ंक्शंस डिस्क्रिप्टर हैं, इस तरह से फ़ंक्शंस को selfतर्क के साथ पहले तर्क के रूप में पारित किया जा सकता है।

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

यदि आप test_methodएक उदाहरण देखते हैं तो आपको "बाध्य विधि" मिल जाएगी:

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

इसी प्रकार आप किसी __get__विधि को मैन्युअल रूप से लागू करके भी बाँध सकते हैं (वास्तव में अनुशंसित नहीं है, सिर्फ उदाहरण के लिए):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

आप इसे "स्व-बद्ध विधि" भी कह सकते हैं:

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

ध्यान दें कि मैंने कोई तर्क नहीं दिया और फ़ंक्शन ने मेरे द्वारा बंधे उदाहरण को वापस कर दिया!

कार्य गैर-डेटा विवरणक हैं !

डेटा-डिस्क्रिप्टर के कुछ अंतर्निहित उदाहरण होंगे property। उपेक्षा getter, setterऔर जानकारी देता है (से डिस्क्रिप्टर HowTo गाइड "गुण" ):deleterproperty

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

यह एक डेटा वर्णनकर्ता यह लागू है जब भी आप की "नाम" को देखने के बाद से है propertyऔर यह केवल काम करता है के साथ सजाया के प्रतिनिधियों @property, @name.setterऔर @name.deleter(यदि है)।

वहाँ मानक पुस्तकालय में कई अन्य वर्णनकर्ता, उदाहरण के लिए कर रहे हैं staticmethod, classmethod

डिस्क्रिप्टर की बात आसान है (हालाँकि आपको शायद ही उनकी आवश्यकता है): एसेसरी एक्सेस के लिए सार कोड। propertyउदाहरण चर पहुंच के functionलिए एक अमूर्त है, तरीकों के लिए एक अमूर्तता प्रदान करता है , उन तरीकों के लिए एक अमूर्तता staticmethodप्रदान करता है जिन्हें इंस्टेंस एक्सेस की आवश्यकता नहीं होती है और classmethodउन तरीकों के लिए एब्सट्रैक्शन प्रदान करता है जिन्हें इंस्टेंस एक्सेस के बजाय क्लास एक्सेस की आवश्यकता होती है (यह थोड़ा सरलीकृत है)।

एक अन्य उदाहरण एक वर्ग संपत्ति होगा

एक मजेदार उदाहरण ( __set_name__पायथन 3.6 का उपयोग करके ) भी एक संपत्ति हो सकती है जो केवल एक विशिष्ट प्रकार की अनुमति देती है:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

तब आप एक वर्ग में डिस्क्रिप्टर का उपयोग कर सकते हैं:

class Test(object):
    int_prop = TypedProperty(int)

और इसके साथ थोड़ा खेल:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

या "आलसी संपत्ति":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

ये ऐसे मामले हैं जहां तर्क को एक सामान्य विवरणक में स्थानांतरित करना समझ में आता है, हालांकि कोई उन्हें (लेकिन शायद कुछ कोड को दोहराने के साथ) अन्य साधनों के साथ हल कर सकता है।

क्या है instanceऔर ownerयहाँ? (में __get__)। इन मापदंडों का उद्देश्य क्या है?

यह इस बात पर निर्भर करता है कि आप कैसे विशेषता देखते हैं। यदि आप एक उदाहरण पर विशेषता को देखते हैं तो:

  • दूसरा तर्क वह उदाहरण है जिस पर आप विशेषता देखते हैं
  • तीसरा तर्क उदाहरण का वर्ग है

यदि आप कक्षा में विशेषता देखते हैं (मान लें कि विवरणक वर्ग पर परिभाषित है):

  • दूसरा तर्क है None
  • तीसरा तर्क वह वर्ग है जहाँ आप विशेषता को देखते हैं

इसलिए मूल रूप से तीसरा तर्क आवश्यक है यदि आप वर्ग-स्तरीय लुक-अप करते समय व्यवहार को अनुकूलित करना चाहते हैं (क्योंकि यह instanceहै None)।

मैं इस उदाहरण को कैसे कॉल / उपयोग करूंगा?

आपका उदाहरण मूल रूप से एक संपत्ति है जो केवल उन मूल्यों को अनुमति देता है जिन्हें परिवर्तित किया जा सकता है floatऔर जो वर्ग के सभी उदाहरणों (और वर्ग पर) के बीच साझा किया जाता है - हालांकि कोई केवल कक्षा में "रीड" एक्सेस का उपयोग कर सकता है अन्यथा आप वर्णनकर्ता उदाहरण को बदल देंगे ):

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

इसीलिए, instanceइसे साझा करने से बचने के लिए मूल्य को संग्रहीत करने के लिए आम तौर पर वर्णनकर्ता दूसरे तर्क ( ) का उपयोग करते हैं । हालांकि कुछ मामलों में उदाहरणों के बीच एक मूल्य साझा करना वांछित हो सकता है (हालांकि मैं इस समय एक परिदृश्य के बारे में नहीं सोच सकता)। हालांकि यह व्यावहारिक रूप से एक तापमान वर्ग पर एक सेल्सियस संपत्ति के लिए कोई मतलब नहीं है ... शायद विशुद्ध शैक्षणिक अभ्यास के रूप में छोड़कर।


सुनिश्चित नहीं है कि ग्राफिक की पारदर्शी पृष्ठभूमि वास्तव में अंधेरे मोड में पीड़ित हो रही है, जिसे स्टैकओवरफ्लो को बग के रूप में रिपोर्ट किया जाना चाहिए।
टीशर्टमैन

@ तीर्थमान मुझे लगता है कि यह छवि के साथ ही एक समस्या है। यह पूरी तरह से पारदर्शी नहीं है ... मैंने इसे ब्लॉग पोस्ट में ले लिया है और यह नहीं जानता कि इसे उचित पारदर्शी पृष्ठभूमि के साथ कैसे बनाया जाए। यह बहुत बुरा है यह अंधेरे पृष्ठभूमि के साथ बहुत अजीब लगता है :(
MSeifert

9

मुझे डिस्क्रिप्टर क्लास की आवश्यकता क्यों है?

बुकियानो रामाल्हो द्वारा फ्लुएंट पायथन से प्रेरित

इमेजिंग आपके पास इस तरह एक वर्ग है

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

हमें एक ऋणात्मक संख्या निर्दिष्ट करने से बचने में वजन और कीमत को मान्य करना चाहिए, यदि हम एक प्रॉक्सी के रूप में वर्णनकर्ता का उपयोग करते हैं तो हम कम कोड लिख सकते हैं।

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

तब वर्ग लाइन को इस तरह परिभाषित करें:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

और हम अधिक सामान्य सत्यापन करने के लिए मात्रा वर्ग का विस्तार कर सकते हैं


1
दिलचस्प उपयोग मामला, जैसा कि यह दिखाता है कि उपयोगकर्ताओं के कई उदाहरणों के साथ बातचीत करने के लिए विवरणक का उपयोग कैसे करें। मुझे शुरू में महत्वपूर्ण बात समझ में नहीं आई: क्लास नेमस्पेस में एक डिस्क्रिप्टर के साथ एक विशेषता बनाई जानी चाहिए (उदाहरण के लिए weight = Quantity(), लेकिन मूल्यों को केवल उपयोग करने वाले उदाहरणों के नामस्थान में सेट किया जाना चाहिए self(उदाहरण के लिए self.weight = 4), अन्यथा यह विशेषता नए मूल्य के लिए रिबाउंड होगी और वर्णनकर्ता खारिज किया जाएगा अच्छा लगा।!
मिनट

मैं एक बात समझ नहीं पा रहा हूं। आप weight = Quantity()वर्ग चर और उसके रूप में परिभाषित कर रहे हैं __get__और __set__उदाहरण चर पर काम कर रहे हैं। कैसे?
टेक्नोक्रेट

0

मैंने एंड्रयू कुक के जवाब से कोड के रूप में (सुझाए गए मामूली बदलावों के साथ) की कोशिश की। (मैं अजगर 2.7 चला रहा हूं)।

कोड:

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

परिणाम:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

अजगर 3 से पहले के साथ, सुनिश्चित करें कि आप वस्तु जो वर्णनकर्ता काम सही ढंग से कर देगा के रूप में से उपवर्ग बनाने प्राप्त जादू पुरानी शैली कक्षाओं के लिए काम नहीं करता।


1
डिस्क्रिप्टर्स केवल नई शैली की कक्षाओं के साथ काम करते हैं। अजगर 2 के लिए। इसका मतलब है कि आपकी कक्षा "ऑब्जेक्ट" से निकली है, जो कि पाइथन 3 में डिफ़ॉल्ट है।
Ivo van der Wijk

0

आप https://docs.python.org/3/howto/descriptor.html#properties देखेंगे

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

1
यह प्रश्न का उत्तर नहीं देता है या कोई उपयोगी जानकारी प्रदान नहीं करता है।
सेबेस्टियन नीलसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.