2025, Oct 07 19:31
dataclass में len को iter के संग: Timer फ़ील्ड गिनने का साफ़ तरीका
Python dataclass में __iter__ और __len__ को एक स्रोत से जोड़ें ताकि Timer फ़ील्ड्स की गिनती सही रहे. default_factory व fields से हार्डकोडिंग से निजात पाएं.
dataclass में len के जरिए आइटम गिनना कभी भी किसी मैजिक नंबर पर निर्भर नहीं होना चाहिए। यदि किसी क्लास इंस्टेंस के पास Timer ऑब्जेक्ट्स का एक सेट है और आप उन्हें पहले से इटरेशन के माध्यम से उपलब्ध कराते हैं, तो अगला स्वाभाविक कदम आकार को प्रोग्राम के जरिए निकालना है। नीचे दिया गया संक्षिप्त पैटर्न बिना अतिरिक्त देखरेख के __len__ को __iter__ के साथ तालमेल में रखता है।
समस्या
आपके पास एक dataclass है जो कई Timer इंस्टेंस को एकत्र करता है। अलग-अलग सबक्लास अलग-अलग timers का सेट घोषित कर सकते हैं, और आप चाहते हैं कि len(instance) उस इंस्टेंस में मौजूद timers की वास्तविक संख्या दिखाए। शुरुआती तरीका एक मैजिक नंबर हार्डकोड करता है — यह नाजुक है और फील्ड बदलते ही आसानी से छूट सकता है।
from dataclasses import dataclass
@dataclass
class CoreTimers:
    def tick(self):
        pass
    def __iter__(self):
        return iter([])
@dataclass
class DeviceTimers(CoreTimers):
    cycleTimer        = tmr.Timer(0)
    vProtectTimer     = tmr.Timer(0)
    aRestTimer        = tmr.Timer(0)
    aMuteTimer        = tmr.Timer(0)
    vRestTimer        = tmr.Timer(0)
    vMuteTimer        = tmr.Timer(0)
    def tick(self):
        self.cycleTimer.tic()
        self.vProtectTimer.tic()
        self.aRestTimer.tic()
        self.aMuteTimer.tic()
        self.vRestTimer.tic()
        self.vMuteTimer.tic()
    def __len__(self):
        return 6
    def __iter__(self):
        return iter([
            self.cycleTimer,
            self.vProtectTimer,
            self.aRestTimer,
            self.aMuteTimer,
            self.vRestTimer,
            self.vMuteTimer,
        ])
असल में हो क्या रहा है
Dataclasses दरअसल साधारण Python क्लास ही हैं, जिन पर एक डेकोरेटर init और अन्य सुविधाएँ जोड़ देता है। यानी __iter__ और __len__ किसी भी क्लास की तरह ही काम करते हैं। ऊपर की दिक्कत यह है कि timers की संख्या को __len__ में एक लिटरल के रूप में लिखा गया है, जो फील्ड जोड़ने या हटाने पर जल्दी असंगत हो जाता है। एक और बारीकी: Timer ऑब्जेक्ट्स को क्लास एट्रिब्यूट के रूप में बनाना मतलब dataclass के सभी इंस्टेंस वही Timer ऑब्जेक्ट्स साझा करेंगे। अगर उद्देश्य हर इंस्टेंस को उसके अपने स्वतंत्र timers देना है, तो प्रति‑इंस्टेंस फैक्ट्री चाहिए।
क्योंकि dataclasses अपने घोषित फील्ड्स को dataclasses.fields के जरिए एक्सपोज़ करती हैं, आप इंस्टेंस का introspection कर सकते हैं, उन सभी एट्रिब्यूट्स को ढूँढ सकते हैं जो timers हैं, और उसी स्रोत से iteration और length दोनों निकाल सकते हैं। Timer जैसा कोई सरल प्रत्यय अपनाने से चयन साफ और स्पष्ट रहता है।
समाधान
इंस्ट्रूमेंटेशन को बेस क्लास में ले जाएँ। introspection के लिए dataclasses.fields का उपयोग करें, Timer प्रत्यय से फ़िल्टर करें, और उसी फील्ड स्रोत के आधार पर __iter__, __len__, और bulk tick लागू करें। स्वतंत्र इंस्टेंस पाने के लिए default_factory का उपयोग करें ताकि हर dataclass इंस्टेंस को नए Timer ऑब्जेक्ट्स मिलें।
from dataclasses import dataclass, fields, field
@dataclass
class CoreTimers:
    @property
    def _timer_names(self):
        for f in fields(self):
            if f.name.endswith("Timer"):
                yield f.name
    def __iter__(self):
        return (getattr(self, name) for name in self._timer_names)
    def __len__(self):
        return len(list(self._timer_names))
    def tick(self):
        for name in self._timer_names:
            getattr(self, name).tic()
@dataclass
class DeviceTimers(CoreTimers):
    cycleTimer: tmr.Timer      = field(default_factory=lambda: tmr.Timer(0))
    vProtectTimer: tmr.Timer   = field(default_factory=lambda: tmr.Timer(0))
    aRestTimer: tmr.Timer      = field(default_factory=lambda: tmr.Timer(0))
    aMuteTimer: tmr.Timer      = field(default_factory=lambda: tmr.Timer(0))
    vRestTimer: tmr.Timer      = field(default_factory=lambda: tmr.Timer(0))
    vMuteTimer: tmr.Timer      = field(default_factory=lambda: tmr.Timer(0))
यह डिज़ाइन iteration और counting को एक ही परिभाषा से निकालता है, इसलिए len(instance) और list(instance) हमेशा मेल खाते हैं, भले ही सबक्लास कितने भी Timer फील्ड घोषित करे। यदि आपके वास्तविक timers किसी साझा नामकरण पैटर्न का पालन नहीं करते, तो _timer_names के अंदर फ़िल्टर को अपनी उपयुक्त कसौटी के अनुसार समायोजित कर सकते हैं।
यह क्यों मायने रखता है
dataclasses.fields पर भरोसा करने से collection जैसी क्लासों में अनुमान और हार्डकोडेड मान हट जाते हैं। यह साझा state से होने वाली सूक्ष्म बग्स से भी बचाता है, क्योंकि हर dataclass इंस्टेंस को अपने अलग Timer ऑब्जेक्ट्स मिलते हैं। साथ ही, तर्क एक जगह केंद्रित रहता है: timers का सेट बदलते ही __iter__, __len__, और bulk operations का व्यवहार बिना क्लास में इधर‑उधर बदलाव किए सही बना रहता है।
निष्कर्ष
क्लास को खुद अपना वर्णन करने दें। dataclasses में introspection पहले से मौजूद है, इसलिए आप घोषित फील्ड्स के आधार पर len को प्रोग्रामेटिक रूप से निकाल सकते हैं, गिनती हार्डकोड करने की ज़रूरत नहीं। Timer जैसा सरल प्रत्यय अपनाएँ, __iter__ और __len__ को उसी एक स्रोत से जोड़ें, और default_factory से timers बनाएँ ताकि इंस्टेंस अलग‑अलग रहें। इतना ही काफी है ताकि आपका timers संग्रह पूर्वानुमेय, रखरखाव‑अनुकूल और मैजिक नंबरों से मुक्त रहे।