क्लास पर डिस्क्रिप्टर सेट करने से डिस्क्रिप्टर को अधिलेखित क्यों किया जाता है?


10

सरल रिप्रो:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

class B(object):
    v = VocalDescriptor()

B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor

इस सवाल का एक प्रभावी डुप्लिकेट है , लेकिन डुप्लिकेट का उत्तर नहीं दिया गया था, और मैंने सीपीथॉन स्रोत में सीखने के अभ्यास के रूप में थोड़ा और खोदा। चेतावनी: मैं मातम में चला गया। मैं वास्तव में उम्मीद कर रहा हूं कि मैं एक कप्तान से मदद ले सकता हूं जो उन पानी को जानता है । मैंने अपने भविष्य के लाभ और भविष्य के पाठकों के लाभ के लिए, उन कॉल को ट्रेस करने में जितना संभव हो सके उतना स्पष्ट होने की कोशिश की।

मैंने __getattribute__वर्णनकर्ताओं पर लागू किए गए व्यवहार पर बहुत सी स्याही देखी है , जैसे कि पूर्ववर्ती खोज। अजगर झलकी में "लागू वर्णनकर्ता" बस नीचे For classes, the machinery is in type.__getattribute__()...मोटे तौर पर मेरे मन में मैं क्या मानना है कि इसी है के साथ सहमत हैं CPython स्रोत में type_getattroहै, जो मैं देख कर नीचे ट्रैक किए गए "tp_slots" तो जहां tp_getattro से भर जाता है । और तथ्य यह है कि B.vशुरू में प्रिंट __get__, obj=None, objtype=<class '__main__.B'>मुझे समझ में आता है।

जो मुझे समझ नहीं आ रहा है, वह असाइनमेंट B.v = 3नेत्रहीन को ट्रिगर करने के बजाए नेत्रहीन को क्यों अधिलेखित करता है v.__set__? मैंने "tp_slots" से एक बार फिर से शुरू होने वाले CPython कॉल को ट्रेस करने की कोशिश की , फिर tp_setattro को पॉप्युलेट किया , फिर टाइप_सेट्टात्रो को देखते हुए_PyObject_GenericSetAttrWithDict के आसपास एक पतला आवरण type_setattro प्रतीत होता है । और वहाँ मेरी उलझन की जड़ है: तर्क के लिए प्रतीत होता है जो एक विवरणक की विधि को पूर्वता देता है !! इसे ध्यान में रखते हुए, मैं यह पता नहीं लगा सकता कि क्यों आंखें मूंदने के बजाय आंखें मूंद कर लिखना चाहिए ।_PyObject_GenericSetAttrWithDict__set__B.v = 3vv.__set__

अस्वीकरण 1: मैंने प्रिंटआउट के साथ स्रोत से अजगर का पुनर्निर्माण नहीं किया, इसलिए मुझे पूरी तरह से यकीन नहीं type_setattroहै कि किस दौरान बुलाया जा रहा है B.v = 3

डिस्क्लेमर 2: VocalDescriptor"विशिष्ट" या "अनुशंसित" डिस्क्रिप्टर परिभाषा को स्पष्ट करने का इरादा नहीं है। यह एक क्रिया है जो मुझे नहीं बताती है कि तरीकों को कब बुलाया जा रहा है।


1
मेरे लिए यह आखिरी पंक्ति में 3 प्रिंट करता है ... कोड ठीक काम करता है
Jab

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

1
@ जाब ओपी अभी भी __get__विधि लागू करने की उम्मीद कर रहा है । B.v = 3प्रभावी रूप से एक के साथ विशेषता को ओवरराइट कर दिया है int
r.ook

2
@ajonharper विशेषता का उपयोग यह निर्धारित करता __get__है कि क्या कहा जाता है, और एक उदाहरण या वर्ग का उपयोग करते समय डिफ़ॉल्ट कार्यान्वयन object.__getattribute__और type.__getattribute__आह्वान __get__करता है। उदाहरण के माध्यम से ही असाइन करना__set__ है।
चेपनर

मुझे लगता है कि वर्णकों के __get__तरीकों को ट्रिगर करने के लिए माना जाता है जब कक्षा से ही बुलाया जाता है। यह कैसे-कैसे गाइड के अनुसार @classmethods और @staticmethods कार्यान्वित किया जाता है । @ जाब मैं सोच रहा हूँ कि B.v = 3क्लास डिस्क्रिप्टर को ओवरराइट करने में सक्षम क्यों है। CPython कार्यान्वयन के आधार पर, मुझे B.v = 3भी ट्रिगर की उम्मीद थी __set__
माइकल कार्ली

जवाबों:


6

आप सही हैं कि B.v = 3बस एक पूर्णांक के साथ वर्णक को अधिलेखित कर दें (जैसा कि यह होना चाहिए)।

के लिए B.v = 3एक विवरणक आह्वान करने के लिए, वर्णनकर्ता metaclass, यानी पर पर परिभाषित किया जाना चाहिए था type(B)

>>> class BMeta(type): 
...     v = VocalDescriptor() 
... 
>>> class B(metaclass=BMeta): 
...     pass 
... 
>>> B.v = 3 
__set__

डिस्क्रिप्टर को चालू करने के लिए B, आप एक उदाहरण का उपयोग B().v = 3करेंगे : यह करेंगे।

B.vगेटक को इनवॉइस करने का कारण डिस्क्रिप्टर इंस्टेंस को स्वयं वापस करने की अनुमति देना है। आमतौर पर आप क्लास ऑब्जेक्ट के माध्यम से डिस्क्रिप्टर पर एक्सेस की अनुमति देने के लिए ऐसा करते हैं:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

अब B.vकुछ उदाहरण लौटाएंगे <mymodule.VocalDescriptor object at 0xdeadbeef>जिनके साथ आप बातचीत कर सकते हैं। यह वस्तुतः वर्णनात्मक वस्तु है, जिसे एक वर्गीय विशेषता के रूप में परिभाषित किया गया है, और इसके राज्य B.v.__dict__को सभी उदाहरणों के बीच साझा किया गया है B

बेशक यह उपयोगकर्ता के कोड पर निर्भर करता है कि वे क्या करना चाहते B.vहैं, यह देखना selfसामान्य रिटर्न है।


1
इस उत्तर को पूरा करने के लिए मैं इसे जोड़ूंगा __get__जिसे उदाहरण विशेषता या वर्ग विशेषता के रूप में कहा जाता है, लेकिन __set__इसे केवल उदाहरण विशेषता के रूप में कहा जाता है। और प्रासंगिक डॉक्स: docs.python.org/3/reference/datamodel.html#object.__get__
sanyash

@wim शानदार !! समानांतर में मैं एक बार और देख रहा था type_setattro कॉल चेन। मैं देखता हूं कि _PyObject_GenericSetAttrWithDict पर कॉल करने से मेरे मामले में टाइप (उस बिंदु B पर) की आपूर्ति होती है।
माइकल कारिलि

भीतर _PyObject_GenericSetAttrWithDict, यह बी के Py_TYPE खींचती है के रूप में tpहै, जो बी के metaclass है ( typeमेरे मामले में), तो यह है metaclass tp कि द्वारा इलाज किया जाता है वर्णनकर्ता शॉर्ट-सर्किट तर्क । तो वर्णनकर्ता सीधे पर परिभाषित B नहीं किया गया है कि शॉर्ट-सर्किट तर्क के द्वारा देखा (इसलिए मेरे मूल कोड में __set__कहा जाता है नहीं), लेकिन एक विवरणक metaclass पर परिभाषित किया गया है शॉर्ट-सर्किट तर्क के द्वारा देखा।
माइकल कारिलि

इसलिए, आपके मामले में जहां मेटाक्लास में एक डिस्क्रिप्टर होता है, __set__उस डिस्क्रिप्टर की विधि को कहा जाता है।
माइकल कारिलि

@ संन्यासी सीधे संपादित करने के लिए स्वतंत्र महसूस करते हैं।
विम

3

किसी भी ओवरराइड को छोड़कर, B.vके बराबर है type.__getattribute__(B, "v"), जबकि b = B(); b.vके बराबर है object.__getattribute__(b, "v")। परिभाषित होने पर दोनों परिभाषाएं __get__परिणाम की विधि का आह्वान करती हैं ।

ध्यान दें, सोचा, कि कॉल __get__प्रत्येक मामले में अलग है। पहले तर्क के रूप में B.vगुजरता Noneहै, जबकि B().vउदाहरण से ही गुजरता है। दोनों मामलों Bमें दूसरे तर्क के रूप में पारित किया जाता है।

B.v = 3दूसरी ओर, के बराबर है type.__setattr__(B, "v", 3), जो आह्वान नहीं करता है __set__

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