2025, Sep 25 13:31
Python में सबक्लास से इंस्टैंस तक टाइप-सुरक्षित मैपिंग (mypy/Pyright अनुकूल पैटर्न)
Python में dict[type[Root], Root] और जनरिक fetch से सबक्लास→इंस्टैंस मैपिंग को टाइप-सुरक्षित बनाएं; mypy/Pyright संगत पैटर्न और cast का व्यावहारिक उपयोग.
जब किसी कोडबेस में कक्षाओं से उनके इंस्टैंस तक का एक ही मैपिंग रखा जाता है, तो स्वाभाविक है कि एक ऐसी विधि चाहिए जो “मैं जो क्लास दूं, उसी का मेल खाता इंस्टैंस लौटाए” — और टाइप चेकर इसे सिद्ध भी करे। चुनौती यह है कि इस संबंध को स्टैटिक विश्लेषण तक कैसे पहुँचाया जाए, ताकि किसी सबक्लास के लिए पूछने पर उसी सटीक सबक्लास का इंस्टैंस मिले, सिर्फ बेस टाइप नहीं।
समस्या की रूपरेखा
एक छोटी-सी इनहेरिटेंस पदानुक्रम और एक रजिस्ट्री की कल्पना करें, जो अवधारणात्मक रूप से सबक्लास को उनके इंस्टैंस से जोड़ती है। सहज टाइप हिंट के रूप में हम एक डिक्शनरी लेते हैं, जिसमें कुंजी सबक्लास होते हैं और मान उसी से मेल खाने वाले इंस्टैंस टाइप के। शुरुआती मसौदा अक्सर कुछ यूँ दिखता है:
from typing import TypeVar
class Root:
    ...
class KindOne(Root):
    ...
class KindTwo(Root):
    ...
class Registry:
    U = TypeVar('U', bound=Root)
    entries: dict[type[U], U] = {}
    def fetch(self, cls: type[U]) -> U:
        return self.entries[cls]
मकसद साफ है: डिक्शनरी KindOne को KindOne() से, और KindTwo को KindTwo() से मैप करती है। fetch विधि को वही सटीक टाइप लौटाना चाहिए जो माँगा गया है, सिर्फ Root नहीं।
वास्तव में क्या हो रहा है
type[U] को U से सीधे जोड़ने वाला dict टाइप हिंट पढ़ने में अच्छा लगता है, लेकिन यह स्टैटिक चेकरों के साथ ठीक नहीं चलता। संबंध “हर ऐसी कुंजी जो एक सबटाइप है, उसका मान उसी सबटाइप का इंस्टैंस होगा” प्रति-कुंजी लागू होता है, पूरे मैपिंग पर समान रूप से नहीं। डिक्शनरी के टाइप पैरामीटर सभी एंट्रियों को एकसमान मानते हैं, और अधिकतर चेकर मनमाने लुकअप पर इस कुंजी–मान निर्भरता को ट्रैक नहीं करते।
अगर मकसद सिर्फ get-जैसे एंट्रीपॉइंट को उजागर करना और आंतरिक स्टोरेज को निजी रखना है, तो एक व्यावहारिक रास्ता मौजूद है। रजिस्ट्री कुंजियों और मानों — दोनों के लिए — बेस क्लास का उपयोग कर मानों को स्टोर कर सकती है, जबकि सार्वजनिक विधि अनुरोधित सबटाइप के लिए लक्षित cast करती है। API उपयोगकर्ताओं को सटीक और साफ़ रिटर्न टाइप मिलता है; भीतर, मैपिंग सरल रहती है और cast उस इरादे को जताने वाला छोटा-सा पुल बन जाती है।
टाइप्ड एंट्रीपॉइंट के साथ काम करने वाला तरीका
नीचे दिया गया पैटर्न mypy और Pyright जैसे लोकप्रिय स्टैटिक चेकरों के साथ काम करता है, और इम्प्लिमेंटेशन को भी सीधा रखता है:
from typing import cast, reveal_type
class Root:
    ...
class KindOne(Root):
    ...
class KindTwo(Root):
    ...
class Registry:
    _store: dict[type[Root], Root] = {KindOne: KindOne(), KindTwo: KindTwo()}
    def fetch[T: Root](self, cls: type[T]) -> T:
        return cast(T, self._store[cls])
reg = Registry()
reveal_type(reg.fetch(KindOne))  # KindOne
reveal_type(reg.fetch(KindTwo))  # KindTwo
मैपिंग पर्दे के पीछे dict[type[Root], Root] रूप में रहती है। सार्वजनिक fetch विधि अपने टाइप पैरामीटर के जरिए सटीक टाइप संबंध रखती है और cast का इस्तेमाल यह दर्शाने के लिए करती है कि निर्माण के समय से ही संग्रहीत इंस्टैंस अनुरोधित क्लास से मेल खाता है।
यह तरीका जानबूझकर न्यूनतम रखा गया है। आप चाहें तो इस बाधा को एन्कोड करने के लिए कोई कस्टम मैपिंग एब्स्ट्रैक्शन बना सकते हैं, लेकिन एक निजी dict और एक अच्छे से टाइप किया गया एक्सेसर से आगे जाने का ठोस कारण नहीं है।
यह क्यों महत्वपूर्ण है
सटीक टाइप वाली API क्लाइंट कोड में अनजाने दुरुपयोग को कम करती है। कॉलर जब KindOne पास करता है, तो उसे KindOne ही वापस मिलता है, और टाइप चेकर इसे पुष्टि करता है। इसका मतलब है कम डाउनकास्ट, कम ignore, और कॉल साइट पर अधिक स्पष्ट अनुबंध।
साथ ही, उम्मीदें जमीन पर रखना भी जरूरी है। cast आपका इरादा जताता है और कॉलर्स की मदद करता है, पर यह चेकर को यह सत्यापित करने पर मजबूर नहीं करता कि निजी मैपिंग हमेशा कुंजी–मान की जोड़ी का पालन करती है। अगर आंतरिक स्टोरेज उस इनवेरिएंट से हट जाए, तो चेकर अकेले इस cast पर आपत्ति नहीं करेगा। यानी, यह पैटर्न क्लाइंट्स को गलतियों से बचाता है, लेकिन आपकी आंतरिक मैपिंग लॉजिक को अपने-आप मान्य नहीं करता।
निष्कर्ष
सबक्लास से उनके इंस्टैंस तक की dict को मॉडल करने के लिए स्टोरेज को निजी रखें और एक सशक्त टाइप्ड एक्सेसर पर भरोसा करें। एंट्रियों को dict[type[Root], Root] के रूप में स्टोर करें, और बेस टाइप से पैरामीटराइज़्ड fetch विधि लागू करें, जो एक छोटे-से cast से विशिष्ट सबटाइप लौटाए। इससे उपयोगकर्ताओं के लिए API सतह साफ़ और सटीक रहती है, जबकि आंतरिक इनवेरिएंट को बनाए रखना आपकी ज़िम्मेदारी रहती है।