2025, Oct 02 03:32
Python injector में tuple[str, str] वाले NewType की बाइंडिंग समस्या और समाधान
Python injector में dependency injection के दौरान NewType और tuple[str, str] की binding क्यों टूटती है, रनटाइम टाइप सीमा क्या है, और TYPE_CHECKING से इसका हल।
Python में dependency injection अक्सर रनटाइम टाइप्स पर निर्भर रहता है, जबकि typing की बदौलत हमारी annotations दिन-ब-दिन ज्यादा अभिव्यंजक हो रही हैं। टकराव तब होता है जब ये दोनों दुनिया मिलती हैं। एक आम स्थिति: injector में tuple[str, str] जैसे parameterized generic पर आधारित NewType को bind करना रनटाइम पर UnknownProvider के साथ विफल हो जाता है।
विफल सेटअप
नीचे दिया गया न्यूनतम उदाहरण एक typed tuple को लपेटने वाले NewType को bind करता है और फिर उसे resolve करने की कोशिश करता है:
import injector
from typing import NewType
PairLabel = NewType("PairLabel", tuple[str, str])
class AppModule(injector.Module):
def configure(self, link: injector.Binder) -> None:
link.bind(PairLabel, PairLabel(("x", "y")))
injector.Injector(modules=[AppModule]).get(PairLabel)
रनटाइम पर यह injector.UnknownProvider तक पहुँचता है, जो शिकायत करता है कि वह interface‑to‑instance जोड़ी के लिए कोई provider नहीं ढूँढ पा रहा। यह तो सिर्फ लक्षण है; असल वजह इस बात में छिपी है कि Python रनटाइम पर parameterized generics को कैसे दर्शाता है।
असल में टूटता क्या है
कसूर NewType का नहीं है। मुद्दा यह है कि tuple[str, str] जैसे parameterized generics वास्तविक रनटाइम टाइप नहीं होते। वे types.GenericAlias के instance होते हैं। जहाँ ठोस (concrete) टाइप चाहिए, वहाँ आप इन्हें नहीं दे सकते; isinstance भी इन्हें दूसरे आर्ग्यूमेंट के रूप में स्वीकार नहीं करता। यह फर्क साफ दिखाई देता है:
>>> type(tuple[str, str])
<class 'types.GenericAlias'>
>>> isinstance(tuple[str, str], type)
False
>>> isinstance(tuple, type)
True
>>> isinstance(("foo", "bar"), tuple[str, str])
Traceback (most recent call last):
...
TypeError: isinstance() argument 2 cannot be a parameterized generic
दूसरे शब्दों में, रनटाइम पर आपके पास tuple[str, str] का कोई वास्तविक instance होता ही नहीं—यह केवल एक स्थिर (static) टाइप निर्माण है। इसलिए DI फ्रेमवर्क के लिए इसे inject करने की कोशिश का कोई मतलब नहीं, क्योंकि वह असली रनटाइम टाइप्स और instances पर काम करता है। इसके उलट, tuple या str जैसे concrete टाइप पर आधारित NewType बिल्कुल ठीक चलता है, क्योंकि वे वास्तविक रनटाइम टाइप्स हैं; उसी कारण से NewType('Baz', tuple) भी काम करेगा।
व्यावहारिक उपाय
इस समस्या से निकलने का तरीका यह है कि static checking के लिए सटीक टाइप्स बनाए रखें, और रनटाइम को bind करने के लिए एक concrete टाइप दें। typing.TYPE_CHECKING आपको दोनों दुनिया के लिए अलग-अलग परिभाषाएँ रखने देता है, बिना चल रहे प्रोग्राम को प्रभावित किए।
import injector
from typing import NewType, TYPE_CHECKING, reveal_type
if TYPE_CHECKING:
PairLabel = NewType("PairLabel", tuple[str, str])
else:
PairLabel = NewType("PairLabel", tuple)
class AppModule(injector.Module):
def configure(self, link: injector.Binder) -> None:
link.bind(PairLabel, PairLabel(("x", "y")))
reveal_type(injector.Injector(modules=[AppModule]).get(PairLabel))
इस तरीके में static analysis को tuple[str, str] (या उसे लपेटता NewType) दिखता है, जबकि रनटाइम को एक ठोस tuple मिलता है। injector binding को सही तरह resolve कर लेता है, क्योंकि वह वास्तविक टाइप के साथ काम कर रहा होता है।
यह क्यों मायने रखता है
Dependency injection कंटेनर इंटरफेस और keys को उसी आधार पर resolve करते हैं जो रनटाइम पर मौजूद है। Parameterized generics रनटाइम में GenericAlias तक सिमट जाते हैं और concrete रनटाइम टाइप की तरह काम नहीं कर सकते। इस सीमा को समझना type hints को उपयोगी बनाए रखता है, बिना static‑only constructs को DI वायरिंग में घुसने दिए। यही वजह है कि str को लपेटता NewType injection परिदृश्यों में ठीक चलता है, जबकि tuple[str, str] को लपेटता NewType नहीं।
निष्कर्ष
Injection के लिए मान bind करते समय concrete रनटाइम टाइप्स का ही सहारा लें। अगर आपको टूलिंग और कोड गुणवत्ता के लिए tuple[str, str] जैसी सटीकता चाहिए, तो if TYPE_CHECKING सुरक्षा के भीतर अपना NewType परिभाषित करें—ताकि type checker को tuple[str, str] दिखाई दे और रनटाइम को साधारण tuple मिले। इससे injector भी संतुष्ट रहता है और स्थिर गारंटी वहीं बनी रहती है जहाँ उनकी जगह है।