2025, Sep 27 21:32

functools.partial की टाइपिंग और dataclass: pyright बनाम mypy

dataclass के साथ functools.partial की टाइपिंग समझें: return type annotation से इंफरेंस क्यों टूटता है, pyright बनाम mypy का फर्क, और सही ऑटो-कम्प्लीशन का उपाय.

dataclasses के साथ functools.partial को टाइप करना: आपकी एनोटेशन्स से इंफरेंस क्यों टूटता है और इसका समाधान क्या है

समस्या

आप किसी dataclass (या किसी भी class) से functools.partial के जरिए एक callable बनाते हैं और चाहते हैं कि टाइप चेकर शेष constructor आर्गुमेंट्स सुझाए। जैसे ही आप उस फ़ैक्टरी मेथड पर स्पष्ट type hints जोड़ते हैं जो partial लौटाती है, completions गायब हो जाती हैं। हिन्ट्स हटा दें, तो pyright तुरंत बता देता है कि कौन-से पैरामीटर बचे हैं।

उदाहरण

नीचे दिया गया उदाहरण एक dataclass और दो classmethod दिखाता है जो आंशिक रूप से लागू constructor बनाते हैं। इनमें से एक पर एनोटेशन है, दूसरा बिना एनोटेशन के है।

from dataclasses import dataclass
from functools import partial
@dataclass
class Sample:
    x: int
    y: int
    @classmethod
    def make_part_typed[U: Sample](cls: type[U], y: int) -> partial[U]:
        return partial(cls, y=y)
    @classmethod
    def make_part(cls, y: int):
        return partial(cls, y=y)
p = Sample.make_part_typed(3)
p(
p2 = partial(Sample, y=3)
p2(
p3 = Sample.make_part(3)
p3(

व्यवहार में, एनोटेटेड संस्करण के बाद p( टाइप करते समय टूलिंग आमतौर पर कुछ नहीं सुझाती, जबकि p2( और p3( को कॉल करते समय x=..., y=... जैसे सुझाव मिलते हैं।

यह क्यों होता है

functools.partial को मानक type annotations के जरिए ठीक से व्यक्त नहीं किया जा सकता। mypy और pyright दोनों इसे सामान्य टाइपिंग कंस्ट्रक्ट्स के बजाय special-casing से संभालते हैं। इसके लिए उनके एल्गोरिद्म अंदरूनी तौर पर लागू किए गए हैं; आप उन्हें उनके कोडबेस में देख सकते हैं — उदाहरण के लिए, mypy में functools का हैंडलिंग और pyright में constructor transform लॉजिक: mypy और pyright

इसी कारण, functools.partial से बने callable के लिए सही टाइप पाने का सबसे अच्छा तरीका है कि partial के परिणाम पर एनोटेशन न दें। एक स्पष्ट return एनोटेशन जोड़ने से विश्लेषक उस अभिव्यक्ति पर अपनी विशेष लॉजिक लागू करने का मौका खो देता है जो partial बनाती है।

इस खास स्थिति में टूल्स के व्यवहार में भी फर्क है। यदि आप return टाइप एनोटेशन छोड़ देते हैं, तो pyright उस फ़ंक्शन का return टाइप अनुमानित कर सकता है जो partial लौटाता है, इसलिए कॉल साइट पर वह सटीक आर्गुमेंट सुझाव देता है। दूसरी ओर, mypy ऐसे फ़ंक्शन के लिए, स्पष्ट return टाइप न होने पर, Any मान लेता है, जिससे शेष आर्गुमेंट्स की सिग्नेचर सही तरह से आगे नहीं बढ़ पाती। mypy के लिए return एनोटेशन जोड़ना भी मदद नहीं करता, क्योंकि इससे वह special-casing हट जाती है जो अन्यथा partial अभिव्यक्ति पर लागू होती।

functools.partial से बने callable के लिए सही टाइप चाहिए, तो एनोटेशन न दें।

यही वजह है कि pyright के साथ बिना एनोटेशन वाला संस्करण बेहतर काम करता है, जबकि एनोटेटेड संस्करण उपयोगी completions खो देता है।

उपाय

return टाइप एनोटेशन दिए बिना partial लौटाएँ, ताकि विश्लेषक अपनी विशेष हैंडलिंग लागू कर सके। प्रोग्राम की तर्कशक्ति वही रखें।

from dataclasses import dataclass
from functools import partial
@dataclass
class Sample:
    x: int
    y: int
    @classmethod
    def make_part(cls, y: int):
        return partial(cls, y=y)
p = Sample.make_part(3)
p(

इस ढंग से, pyright आंशिक रूप से लागू किए गए constructor के शेष पैरामीटर का अनुमान लगा सकता है और उन्हें ऑटो-कम्प्लीशन में दिखा सकता है।

mypy के लिए यह स्थिति साफ़-सुथरे ढंग से मॉडल करना लगभग असंभव है: return टाइप के बिना वह फ़ंक्शन को Any लौटाने वाला मानता है; और return टाइप देने पर special-casing लागू ही नहीं होती। तकनीकी तौर पर कुछ उन्नत वर्कअराउंड मौजूद हैं (जैसे partial अभिव्यक्ति लेने वाले डेकोरेटर फ़ैक्टरी से फ़ंक्शन को बदलना), लेकिन वे आमतौर पर मूल समस्या से कहीं अधिक जटिल हो जाते हैं।

यह क्यों महत्वपूर्ण है

सटीक ऑटो-कम्प्लीशन और कॉल-साइट वैलिडेशन इस बात पर निर्भर करते हैं कि टाइप चेकर आंशिक अनुप्रयोग के बाद callable की सिग्नेचर का कितना भाग शेष है, यह कितना समझ पाता है। यदि आप partial बनाने वाले फ़ंक्शन पर return एनोटेशन थोपते हैं, तो यह समझ खो सकती है और उन संपादकों में डेवलपर अनुभव तथा स्टैटिक जाँच घट सकती है जो विश्लेषक की विशेष लॉजिक पर निर्भर हैं।

निष्कर्ष

यदि आपको functools.partial के लिए शेष आर्गुमेंट्स का सही अनुमान चाहिए, तो ऐसे फ़ंक्शनों के return टाइप का एनोटेशन करने से बचें जो partial लौटाते हैं। इससे pyright अपनी special-casing लागू कर पाता है और सटीक completions देता है। ध्यान दें कि यहाँ mypy का व्यवहार अलग है: एनोटेशन छोड़ने पर वह Any मान लेता है, और एनोटेशन जोड़ने पर विशेष हैंडलिंग हट जाती है। यदि डायनेमिक व्यवहार चाहिए और प्रोटोकॉल उपयुक्त नहीं हैं, तो फ़ैक्टरी को बिना एनोटेशन के रखें और विश्लेषक की अंतर्निहित इंफरेंस पर भरोसा करें।

यह लेख StackOverflow पर प्रश्न (लेखक: Ziur Olpa) और dROOOze के उत्तर पर आधारित है।