2025, Oct 03 15:33

WindowsPath सबक्लास में _from_parts त्रुटि: Python 3.11–3.13 समाधान

pathlib के निजी _from_parts पर निर्भर WindowsPath सबक्लास 3.12/3.13 में क्यों टूटता है, 3.11 में सही इम्पोर्ट क्या है, और नए वर्शन हेतु कंस्ट्रक्टर फिक्स के कदम जानें.

जब आप कोई सुरक्षा अनुसंधान टूल चलाते हैं और वह तुरंत ही स्टैंडर्ड लाइब्रेरी पर ठोकर खा जाता है, तो इसकी जड़ में अक्सर संस्करण का मेल न बैठना या गलत/अतिरिक्त इम्पोर्ट होता है। WindowsDowndate के साथ भी यही घटता है, जब कोड ऐसी pathlib की आंतरिक चीज़ों पर निर्भर करता है जो अलग-अलग Python रिलीज़ में बदलती रहती हैं।

कहाँ और क्या टूट रहा है

समस्या एक हेल्पर मॉड्यूल में सामने आती है, जहाँ WindowsPath का एक सबक्लास बनाया गया है और pathlib से एक निजी कंस्ट्रक्टर‑हेल्पर खींचा गया है। दिक्कत वाले सेटअप में यह कुछ ऐसा दिखता है:

import os
from typing import Any, TypeVar, Type, Self
from pathlib import WindowsPath, _from_parts
class PathPlus(WindowsPath):
    """
    WindowsPath का विस्तार, जो पर्यावरण चलों (env vars) का विस्तार करता है और NT पथ उजागर करता है।
    """
    TPathPlus = TypeVar("TPathPlus")
    def __new__(cls: Type[TPathPlus], raw_path: str, *extra: Any, **kw: Any) -> TPathPlus:
        expanded = os.path.expandvars(raw_path)
        extra = (expanded,) + extra
        obj = cls._from_parts(extra)
        return obj
    @property
    def nt_path(self: Self) -> str:
        return f"\??\{self.full_path}"
    @property
    def full_path(self: Self) -> str:
        return str(self)

यह वेरिएंट pathlib से _from_parts आयात करता है और __new__ में cls._from_parts(...) बुलाता है। यहीं से बात बिगड़ती है।

यह क्यों विफल होता है

रिपॉज़िटरी के मौजूदा कोड में आयात सिर्फ from pathlib import WindowsPath है। pathlib से _from_parts खींचना नज़रअंदाज़ करने योग्य ही नहीं, गलत भी है। Python 3.11 में _from_parts एक आंतरिक मेथड के रूप में PurePath पर मौजूद है, टॉप-लेवल इम्पोर्ट योग्य प्रतीक के रूप में नहीं। Python 3.12 और 3.13 में यह आंतरिक हिस्सा बदल गया, और यह मेथड पहले जैसी जगह पर नहीं मिलता। प्रोजेक्ट खुद बताता है कि इसे Python 3.11.9 पर टेस्ट किया गया था—यानी कार्यान्वयन यह मानकर चलता है कि यह आंतरिक मेथड क्लास हायरार्की पर उपलब्ध होगा, अलग से इम्पोर्ट नहीं किया जाएगा।

पूरा ट्रेसबैक यह पक्का कर देगा कि आप इम्पोर्ट फेलियर देख रहे हैं या एट्रिब्यूट एरर, लेकिन कोड और वर्शन नोट्स से असंगति साफ़ है।

दो व्यावहारिक समाधान

पहला रास्ता है अपने वातावरण को प्रोजेक्ट की अपेक्षा के अनुरूप करना। यदि आप Python 3.11 चला रहे हैं, तो बस गलत इम्पोर्ट हटा दें और इनहेरिटेड मेथड पर भरोसा करें। दूसरा रास्ता है 3.12/3.13 पर बने रहकर वही गुम हिस्से अपने सबक्लास में जोड़ देना, जैसा नीचे दिखाया है।

Python 3.11 के लिए सुधार: गलत इम्पोर्ट हटा दें

क्लास ज्यों की त्यों रखें और इम्पोर्ट लाइन से _from_parts निकाल दें। टेस्ट किए गए वातावरण में इनहेरिटेड इंटरनल्स पर्याप्त हैं।

import os
from typing import Any, TypeVar, Type, Self
from pathlib import WindowsPath
class PathPlus(WindowsPath):
    TPathPlus = TypeVar("TPathPlus")
    def __new__(cls: Type[TPathPlus], raw_path: str, *extra: Any, **kw: Any) -> TPathPlus:
        expanded = os.path.expandvars(raw_path)
        extra = (expanded,) + extra
        obj = cls._from_parts(extra)
        return obj
    @property
    def nt_path(self: Self) -> str:
        return f"\??\{self.full_path}"
    @property
    def full_path(self: Self) -> str:
        return str(self)

यह रिपॉज़िटरी के मौजूदा इम्पोर्ट से मेल खाता है और “tested with python 3.11.9” के अनुरूप है।

Python 3.12/3.13 के लिए अग्रिम‑संगत विकल्प

यदि आप नए Python पर ही रहना चाहते हैं, तो आवश्यक कंस्ट्रक्टर‑हेल्पर्स अपने सबक्लास में दे दें। यह 3.11 की आंतरिक निर्भरता को दर्शाता है और उस संगत आर्ग्युमेंट पार्सर को भी शामिल करता है जिस पर कंस्ट्रक्टर निर्भर है।

import os
from typing import Any, TypeVar, Type, Self
from pathlib import WindowsPath, PurePath
class PathPlus(WindowsPath):
    TPathPlus = TypeVar("TPathPlus")
    def __new__(cls: Type[TPathPlus], raw_path: str, *extra: Any, **kw: Any) -> TPathPlus:
        expanded = os.path.expandvars(raw_path)
        extra = (expanded,) + extra
        obj = cls._from_parts(extra)
        return obj
    @classmethod
    def _from_parts(cls, items):
        inst = object.__new__(cls)
        drv, root, parts = inst._parse_args(items)
        inst._drv = drv
        inst._root = root
        inst._parts = parts
        return inst
    @classmethod
    def _parse_args(cls, items):
        chunks = []
        for piece in items:
            if isinstance(piece, PurePath):
                chunks += piece._parts
            else:
                fs_val = os.fspath(piece)
                if isinstance(fs_val, str):
                    chunks.append(str(fs_val))
                else:
                    raise TypeError(
                        "argument should be a str object or an os.PathLike "
                        "object returning str, not %r" % type(fs_val)
                    )
        return cls._flavour.parse_parts(chunks)
    @property
    def nt_path(self: Self) -> str:
        return f"\??\{self.full_path}"
    @property
    def full_path(self: Self) -> str:
        return str(self)

इस तरह आंतरिक कंस्ट्रक्टर का मार्ग क्लास के भीतर ही समाहित हो जाता है, और आप विभिन्न संस्करणों में pathlib के निजी हिस्सों की स्थिरता पर निर्भर नहीं रहते।

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

जो प्रोजेक्ट्स स्टैंडर्ड लाइब्रेरी के निजी या अर्ध‑निजी इंटरनल्स पर टिके होते हैं, वे छोटे‑छोटे Python अपग्रेड्स में भी नाज़ुक पड़ते हैं। यहाँ, Python 3.11 और 3.12/3.13 के बीच pathlib की आंतरिक बनावट में बारीक अंतर ही टूल को इम्पोर्ट समय पर रोक देता है। सटीक विफलता को पूरे ट्रेसबैक से पुष्ट करना और अपने वातावरण को रिपॉज़िटरी के घोषित बेसलाइन से मिलाना अनावश्यक डिबगिंग से बचाता है। यदि आपको नए इंटरप्रिटर्स पर रहना ही है, तो जितना न्यूनतम लॉजिक चाहिए, उसे अलग कर अपने कोड में शामिल करना आपको अनब्लॉक रखेगा और अपस्ट्रीम व्यवहार के क़रीब भी।

मुख्य निष्कर्ष

पहले यह सुनिश्चित करें कि आपके पास मौजूद रिपॉज़िटरी वर्शन मौजूदा कोड—खासकर इम्पोर्ट्स—से मेल खाता है। तेज़ सफलता के लिए Python को उसी वर्शन पर संरेखित करें जिसे प्रोजेक्ट ने परीक्षणित बताया है—यहाँ 3.11.9। यदि डाउनग्रेड संभव नहीं, तो ऊपर दिखाए अनुसार वे छोटे‑छोटे हेल्पर्स जोड़ें जिन पर कोड निर्भर है, ताकि 3.12/3.13 पर संगतता लौट आए। और हमेशा पूरा ट्रेसबैक संभालकर रखें; ऐसी असंगतियाँ तुरंत पकड़ में आ जाती हैं।

यह लेख StackOverflow पर प्रश्न पर आधारित है, जिसे oz hagevermeleh ने पूछा था, और furas के उत्तर पर।