2 पॉइंट द्वारा GN⁺ 2025-10-04 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Python में module level पर सभी imports घोषित करना सामान्य परंपरा है
  • लेकिन program चलने पर अनावश्यक dependency modules भी तुरंत load हो जाते हैं, जिससे startup speed और memory usage की समस्या पैदा होती है
  • पहले function के अंदर import जैसी तकनीकों से manual lazy import का काफी उपयोग होता था, लेकिन इसमें maintenance और dependency management कठिन होने की कमी थी
  • इस PEP 810 में local, explicit, controlled, granular नए lazy keyword के साथ explicit lazy import syntax पेश किया गया है
  • इस feature से module केवल वास्तव में जरूरत पड़ने पर load होंगे, जिससे startup delay और memory waste में सुधार के साथ code structure की transparency भी बनी रहेगी

Python import की मौजूदा स्थिति और समस्याएँ

  • Python में आमतौर पर module के सबसे ऊपर import statements लिखने की परंपरा व्यापक है
  • इस तरीके से duplication कम होता है, import dependency structure एक नज़र में दिखती है, और एक बार import करके runtime overhead न्यूनतम रखा जाता है
  • लेकिन program execution के समय पहला module (main) load होते ही, वास्तव में उपयोग न होने वाले कई dependency modules तक तुरंत पढ़े जाने वाली chain import की स्थिति आसानी से बन जाती है
  • खासकर CLI tools में केवल help चलाने पर भी दर्जनों modules पहले से load हो जाते हैं, यानी हर subcommand पर अनावश्यक overhead होता है

मौजूदा विकल्प और उनकी समस्याएँ

  • import का समय टालने के लिए imports को function के अंदर ले जाने जैसी manual तकनीकें अक्सर उपयोग की जाती हैं
  • लेकिन इस तरीके से consistency और maintainability घटती है, और पूरी dependency समझना भी कठिन हो जाता है
  • standard library के विश्लेषण के अनुसार, performance-sensitive code में कुल imports का लगभग 17% पहले से ही import delay के उद्देश्य से function या method के अंदर इस्तेमाल हो रहा है
  • import delay से जुड़े tools में importlib.util.LazyLoader और third-party lazy_loader package मौजूद हैं, लेकिन वे सभी cases को cover नहीं करते या कोई एक standard अनुपस्थित है

PEP 810: explicit lazy import की शुरुआत

  • नया lazy soft keyword जोड़ा गया है (यह केवल खास context में अर्थ रखता है, और variable name आदि के रूप में भी इस्तेमाल हो सकता है)

  • lazy केवल import statement के पहले इस्तेमाल किया जा सकता है, और function/class/with/try blocks या star import में इसका उपयोग नहीं होगा

  • हर import statement के स्तर पर इसे स्पष्ट रूप से अलग करके इस्तेमाल किया जाता है ताकि उपयोग के समय तक module loading टाली जा सके

    lazy import 모듈명
    lazy from 모듈명 import 이름
    

explicit lazy import की implementation और syntactic rule

  • syntax error वाले cases:

    • function के अंदर, class के अंदर, try/with, और star import (*) सभी में निषिद्ध
  • उपयोग का उदाहरण:

    import sys
    lazy import json
    print('json' in sys.modules)  # False (अभी तक load नहीं हुआ)
    result = json.dumps({"hello": "world"})  # पहले उपयोग पर load
    print('json' in sys.modules)  # True (lazy module load पूरा)
    
  • module स्तर पर __lazy_modules__ attribute में string list के रूप में lazy targets घोषित किए जा सकते हैं

    __lazy_modules__ = ["json"]
    import json  # lazy के रूप में process होगा
    

global flag और filter के जरिए behavior control

  • global flag या filter function का उपयोग करके module level या पूरे runtime में lazy लागू करना नियंत्रित किया जा सकता है

  • filter function की मदद से कुछ specific modules पर eager import exception भी लगाया जा सकता है

    def my_filter(importer, name, fromlist):
        if name in {'problematic_module'}:
            return False  # eager import
        return True  # lazy import
    sys.set_lazy_imports_filter(my_filter)
    

runtime behavior और error handling

  • lazy import के साथ वास्तविक import import statement के समय नहीं, बल्कि name के पहले access पर होता है

  • import fail होने पर exception chain (traceback chaining) के जरिए definition location और error location दोनों साफ़ दिखते हैं

    lazy from json import dumsp  # typo
    result = dumsp({"key": "value"})  # वास्तविक access पर ImportError
    

memory और performance लाभ

  • delayed modules केवल sys.lazy_modules set में दिखते हैं और वास्तविक उपयोग से पहले sys.modules में register नहीं होते
  • उपयोग के बाद वे सामान्य module object से replace हो जाते हैं, और बिना अतिरिक्त performance penalty के इस्तेमाल किए जा सकते हैं
  • वास्तविक workload environment में startup delay 50~70% कम और memory 30~40% कम होने का प्रभाव देखा गया है

behavior का सारांश

  • lazy object के पहले access पर reification (वास्तविक import और replacement) होता है
  • बाहरी code जब module के __dict__ को access करता है, तब सभी lazy objects को force-load किया जाता है (reification)
  • globals() से dictionary निकालने पर lazy proxy बना रहता है, इसलिए direct access की जरूरत होती है

type annotation और TYPE_CHECKING optimization

  • lazy from 모듈 import 이름 के जरिए केवल type के लिए उपयोग होने वाले imports पर runtime cost ZERO की गारंटी मिलती है
  • इससे पुराने from typing import TYPE_CHECKING conditional pattern की जगह code अधिक concise और स्पष्ट हो जाता है

पुराने PEP 690 से अंतर और implementation की विशेषताएँ

  • PEP 810 एक explicit, per-import, simple proxy object based opt-in structure है
  • जबकि PEP 690 global और implicit lazy import structure था

सावधानियाँ और modules के बीच interaction

  • star import (*) को lazy के रूप में support नहीं किया गया है (हमेशा eager)
  • custom import hook और loader reification के समय वैसे ही काम करेंगे
  • multi-threaded environment में भी thread-safe तरीके से केवल एक बार import और सुरक्षित binding की गारंटी है
  • एक ही module का lazy और eager दोनों तरह से उपयोग होने पर eager वाला हमेशा प्राथमिकता लेता है

code adoption और migration guide

  • मौजूदा code में लागू करते समय profiling के आधार पर केवल जरूरी imports को lazy में बदलना और gradual adoption की सिफारिश की गई है
  • __lazy_modules__ का उपयोग करने पर Python 3.15 से कम versions में भी compatibility मिलती है

अन्य प्रमुख प्रश्न और उत्तर बिंदु

  • import-time side effects (जैसे registration pattern) पहले access तक delay होंगे। यदि side effect आवश्यक हो तो explicit initialization function pattern की सिफारिश है
  • circular import समस्या lazy import से पूरी तरह हल नहीं होती (राहत तभी मिलती है जब access वास्तव में देर से हो)
  • hot-path performance में first use के बाद lazy check पूरी तरह हट जाता है और अपने आप optimize हो जाता है (bytecode adaptive specialization)
  • sys.modules में वास्तविक module केवल reification (पहले उपयोग) के बाद ही register होता है
  • importlib.util.LazyLoader के विपरीत इसमें अलग configuration की जरूरत नहीं, performance बनी रहती है, और standard syntax अधिक स्पष्ट है

निष्कर्ष

  • PEP 810, Python import statements में lazy keyword जोड़कर, subcommand CLI, बड़े applications, type annotations जैसी कई स्थितियों में अनावश्यक module loading से आने वाली performance समस्याओं को सरल और predictable तरीके से optimize करने देता है
  • नया keyword लागू करने के समय और target को बारीकी से नियंत्रित कर सकता है, इसलिए यह production services में gradual adoption और performance tuning के लिए उपयुक्त है
  • यह Python import system का व्यावहारिक विकास है, जो visibility, maintainability, performance तीनों जरूरतों को एक साथ पूरा करता है

1 टिप्पणियां

 
GN⁺ 2025-10-04
Hacker News राय
  • मेरा llm.datasette.io CLI टूल plugins को support करता है, लेकिन llm --help जैसे command में भी बहुत slow startup time की शिकायतें मिल रही थीं। जांच करने पर पता चला कि popular plugins default रूप से pytorch जैसे भारी packages import कर रहे थे, जिससे पूरा startup block हो रहा था। इसलिए मैंने plugin authors के docs में यह सलाह दी कि dependencies को सिर्फ function के अंदर, जरूरत होने पर import करें (संबंधित docs लिंक)। लेकिन अगर Python language level पर इसका support मिले तो यह कहीं बेहतर होगा

    • आज ही tool में इस feature को implement किया जा सकता है (व्याख्या लिंक), लेकिन यह तरीका पूरे process पर globally apply होता है, इसलिए अगर numpy को lazy import किया जाए तो उसके submodule imports भी सभी lazy हो जाते हैं। नतीजतन अगर numpy का पूरा हिस्सा जरूरी न हो तो वह कभी import ही नहीं होगा, लेकिन जरूरत पड़ने पर module imports runtime भर अनपेक्षित रूप से बिखरे हुए तरीके से हो सकते हैं। आगे के प्रयोग में यह भी मिला कि import foo.bar.baz जैसा import करने पर foo और foo.bar अभी भी तुरंत load होते हैं और सिर्फ foo.bar.baz lazy होता है। शायद PEP में mostly शब्द इसी वजह से है। अगर मैं अपनी implementation को और सुधारूं तो शायद इसे ठीक कर सकूं

    • पहले command line parse करके --help जैसे options को import के बिना handle करने का तरीका recommend करूंगा। सिर्फ जब वास्तव में जरूरत हो तभी import चलाएं, या आसान भाषा में कहें तो simple command options process होने के बाद भी अगर काम बाकी हो तभी import करने वाला design भी ठीक है

  • Lazy import का proposal पहले भी आ चुका है, और सबसे हाल में 2022 में reject हुआ था (संबंधित चर्चा लिंक)। मेरी याद में lazy import Meta के CPython variant Cinder में पहले से मौजूद है, और इस बार का PEP भी Cinder पर काम करने वालों ने आगे बढ़ाया है। चर्चा का फोकस था: "opt-in या opt-out?" "scope कितना हो?" "क्या इसे CPython build flag के रूप में जोड़ना चाहिए?" वगैरह। आखिर में Steering Council ने import behavior दो हिस्सों में बंट जाने की complexity के कारण इसे reject किया था। उम्मीद है यह proposal इस बार जरूर pass हो, क्योंकि मैं यह feature सच में इस्तेमाल करना चाहता हूं

    • खासकर यह बात पसंद आई कि यह opt-in है, fine-grained level पर apply किया जा सकता है, और global off switch भी है। कई constraints के भीतर यह बहुत अच्छी तरह तैयार की गई spec लगती है

    • मैं भी चाहता हूं कि यह proposal pass हो, लेकिन बहुत आशावादी नहीं हूं। यह बहुत सारा code तोड़ेगा और अनपेक्षित समस्याएं भी लाएगा। import statement में मूल रूप से side effects होते हैं, और उनके timing बदलने से लंबे समय तक source का पता न चलने वाले bugs से जूझना पड़ सकता है। यह बेवजह डर फैलाना नहीं है, बल्कि वास्तविक कारणों वाली चिंता है। Lazy import सिर्फ Meta में ही क्यों रहा, इसकी वजह भी यही है—इसे संभालने के लिए Meta जैसी resources चाहिए। बहुत से लोग सिर्फ इतना देख रहे हैं कि "pandas, numpy, या मेरा उलझा हुआ weird module बहुत slow है, तो तेज हो जाए तो अच्छा होगा", लेकिन असल में Python का import system कैसे काम करता है, यह जानने वाले लोग कम हैं। यहां तक कि lazy import कैसे implement होता है यह जाने बिना भी बहुत लोग समर्थन कर रहे हैं। PEP 690 में कई downsides बताए गए हैं—उदाहरण के लिए decorators का इस्तेमाल करके functions को central registry में add करने वाला code टूट जाता है। खास तौर पर Dash library JavaScript-based interface और Python callbacks को import time पर decorators से जोड़ती है, इसलिए अगर import lazy हो जाए तो ऐसा frontend पूरी तरह टूट जाएगा। बहुत सारे users वाली services भी तुरंत टूट सकती हैं। लोग कहते हैं, “यह opt-in है, suit न करे तो lazy import बंद कर दो,” लेकिन अगर import transitive हो तो? अगर frontend पूरी तरह initialize होने के बाद ही कोई critical process शुरू होना चाहिए तो? कई लोगों के code और libraries से जुड़े ecosystem में क्या असर होगा, कौन जानता है? यह type hints जैसा बदलाव नहीं है; इसका runtime behavior पर सीधा असर पड़ता है। import statement लगभग हर वास्तविक Python code में होता है, इसलिए lazy आने का मतलब execution model का मूल रूप से बदल जाना है। इसके अलावा PEP में बताए गए और भी अजीब cases हैं। यह दिखने से कहीं ज्यादा कठिन समस्या है

    • import torch==2.6.0+cu124, import numpy>=1.2.6 जैसी version-specified imports हों, और एक ही Python environment में package के कई versions को साथ install/import किया जा सके, तो बहुत अच्छा होगा। अब conda/virtualenv/docker/bazel वाले नर्क से निकलना चाहिए

  • मुझे यह बुरा तो नहीं लगता, लेकिन बहुत उत्साह भी नहीं है। इस रूप में तो लगभग हर import के आगे lazy लगाना पड़ेगा, कुछ गिने-चुने cases को छोड़कर जहां eager import सच में जरूरी हो। बाकी हर जगह lazy लगाने से code गंदा लगेगा। और चूंकि इसे default behavior बनाने की कोई योजना नहीं है, यह verbosity हमेशा बनी रहेगी। मुझे तो बेहतर लगता कि module side से lazy loading के लिए opt-in declare किया जाता और import syntax बदलता ही नहीं। तब सिर्फ बड़ी libraries को laziness का ध्यान रखना पड़ता। हां, ऐसा करने पर interpreter को import time पर filesystem खंगालना पड़ता और दूसरी कमियां भी होतीं

    • अगर सभी लोग बिना खास समस्या के lazy import बहुत इस्तेमाल करेंगे, तो इसका मतलब है कि lazy ही default होना चाहिए था और <i>eager</i> optional keyword होना चाहिए था। Python में ऐसा paradigm shift पहली बार नहीं होगा। v2 में eagerly list बनाने वाले कई constructs v3 में generator हो गए थे और कोई बड़ी समस्या नहीं हुई

    • अगर command line flag से पूरे Python process के सभी module imports को lazy बनाया जा सके, तो मैं तुरंत इस्तेमाल करूंगा। Scripts या बहुत simple code को छोड़ दें, तो module load पर side effects होना सच में avoid किया जाना चाहिए

    • मुझे नहीं लगता कि module side को तय करना चाहिए कि lazy loading हो या नहीं। सिर्फ caller ही जानता है कि उसे lazy load चाहिए या नहीं, इसलिए option importing code की तरफ होना उचित है। कोई भी module lazy load किया जा सकता है, और side effects होने पर भी caller शायद उन्हें delay करना चाहे

    • अच्छा होता अगर pyproject.toml में regex के जरिए lazy loading options लिखे जा सकते

    • अतीत में type hints, walrus, asyncio, dataclasses जैसी नई features आने पर भी लोगों ने ऐसी ही चिंताएं जताई थीं, लेकिन वास्तव में बहुत सारे लोगों ने एक साथ उन्हें अपनाया नहीं और न ही सारे पुराने patterns बदल गए। बहुत से users आज भी modernized Python 2.4-स्तर की features ही इस्तेमाल करते हैं, और उससे भी वे काफी productive हैं। 20 साल से चीजें ठीक चल रही हैं, इसलिए शायद बड़ा संकट नहीं होगा

  • अगर दिलचस्पी हो, तो context manager के रूप में lazy import को बहुत सुविधाजनक तरीके से implement करने वाला lazyimp पेश है। आम तौर पर सिर्फ import statement को with block में wrap करना होता है, इसलिए यह मौजूदा tools के साथ भी अच्छी तरह काम करता है। Debugging चाहिए तो आसानी से eager import पर switch किया जा सकता है। यह C extension के जरिए frame के f_builtins को बदलकर importlib hook से भी ज्यादा powerful बनाता है। यह perfect नहीं है, लेकिन thread-safe version और global handler version भी हैं। शुरू में मैं सावधान था, लेकिन अब codebase का लगभग अधिकतर हिस्सा इसी पर ले आया हूं। व्यवहार में कोई समस्या नहीं हुई (module-wise registration handling को छोड़कर), और speed improvement इतनी ज्यादा महसूस हुई कि मैं बहुत संतुष्ट हूं

  • Python linters का imports को file के top पर रखने के लिए मजबूर करना सच में परेशान करने वाला है। हर बार obvious lazy import technique इस्तेमाल करने पर lint error आ जाता है। यह सिर्फ performance issue नहीं है। उदाहरण के लिए, अगर platform-specific library चाहिए हो तो उसे सिर्फ उसी platform पर import करना चाहेंगे, लेकिन top-level import की मजबूरी से import शुरुआत में ही fail हो सकता है

    • ऐसे में लगता है कि linter को ही ठीक करना पड़ेगा

    • ज्यादातर linters में #noqa E402 जैसे comment से इसे ignore किया जा सकता है

  • कहा जाता है कि LazyLoader class के जरिए कुछ हद तक automatic lazy import संभव है, लेकिन Python import internals का इस्तेमाल करने का तरीका इतना साफ नहीं है कि Stack Overflow की व्याख्या भी बहुत सुंदर नहीं लगती (संबंधित Q&A)। इसलिए programmer ने खुद बिना explicit syntax के सभी imports को lazy बनाने वाला proof-of-concept code लिखकर देखा

import sys
import threading  # 파이썬 3.13에선 필요, REPL에서만이라도
from importlib.util import LazyLoader  # 이건 반드시 즉시 import해야 함!
class LazyPathFinder(sys.meta_path[-1]):  # _frozen_importlib_external.PathFinder 상속
  @classmethod
  def find_spec(cls, fullname, path=None, target=None):
    base = super().find_spec(fullname, path, target)
    base.loader = LazyLoader(base.loader)
    return base
sys.meta_path[-1] = LazyPathFinder

इस तरह meta path finder को wrap करने वाले wrapper से बदल दिया जाता है, जो loader को LazyLoader से replace करता है। import चलने पर module नाम वास्तव में <class 'importlib.util._LazyModule'> से bind होता है, और attribute access होने पर असली module load होता है। प्रयोग कोड:

import this  # 아무런 결과도 안 나옴
print(type(this))  # <class 'importlib.util._LazyModule'>
rot13 = this.s  # Zen이 출력됨, 이 시점에 모듈 로딩
print(type(this))  # <class 'module'>

लेकिन PEP में mostly शब्द का सटीक मतलब क्या है, यह अभी भी स्पष्ट नहीं है

  • लगता है कि lazy import में thread safety risk को कम करके आंका गया है। Import कब चलेगा, किस thread में चलेगा, कौन-सा lock पकड़ेगा—इनमें से किसी चीज़ की भविष्यवाणी नहीं की जा सकती, importer lock के अलावा और किसी बात की गारंटी नहीं है। पहले module import time पर कोई risky code चलता भी था, तो वह अधिकतर single-threaded initialization के दौरान ही होता था, इसलिए बड़ी समस्या नहीं बनती थी। Lazy होने पर errors सच में अनिश्चित और Heisenbug जैसे तरीके से सामने आ सकते हैं। Function-level import में भी ऐसा risk हो सकता है, लेकिन कम से कम यह अनुमान तो होता है कि वह explicit code की शुरुआत में चलेगा

  • यह अच्छी feature लगती है। समझाना भी आसान है, practical use cases भी हैं, और इसका scope भी ठीक-ठाक है (global use और simple keyword style)। मुझे पसंद आई

    • हाल के PEPs में user perspective से यह सबसे साफ-सुथरों में से एक लगता है। इस पारंपरिक syntax bikeshedding के बाद असली परिणाम देखने की उत्सुकता है

    • मुझे लगता है यह PEP बहुत सावधानी से तैयार किया गया है—real-world और edge case validation, सही compromises, जरूरत से ज्यादा न होना, और कई बार refine किया जाना इसकी ताकत हैं। खास तौर पर इतनी विविध global communities वाली बड़ी language के core system को छूना बहुत जोखिम भरा हो सकता है, इसलिए उस कठिनाई को देखते हुए यह खास तौर पर प्रभावशाली है

    • उम्मीद है कि PEP-690 के reject होने से पर्याप्त सीख ली गई होगी। हमने भी अपने codebase में ऐसा कुछ खुद implement करने की कोशिश की थी, लेकिन कभी usable स्तर तक ठीक से काम नहीं कर पाया

  • Lazy import का खतरा यह है कि लंबे समय तक चलने वाली services में यह अनपेक्षित runtime errors पैदा कर सकता है। Fast startup इसका फायदा लगता है, लेकिन बदले में यह जोखिम आता है कि code execution बीच में import failure से रुक जाए। इसके अलावा ऐसे edge cases भी हो सकते हैं जहां program start के समय यह भरोसे से नहीं कहा जा सके कि आगे क्या-क्या import होगा

    • फिर भी यह एक वास्तविक समस्या है जिसे हल करना ही होगा। यह सिर्फ startup speed का मुद्दा नहीं है; बड़ी dependencies आ जाएं तो Python startup बेहिसाब slow हो जाता है। बड़े projects हर user के लिए भारी libraries bundle भी नहीं कर सकते जिनका वे उपयोग ही नहीं करते, इसलिए developers पहले से ही और भी अजीब workarounds इस्तेमाल कर रहे हैं, जो अपने साथ और समस्याएं लाते हैं। सिर्फ function-level imports को बार-बार छिपाने या दोहराने की मजबूरी हट जाए, तो भी यह बड़ी प्रगति होगी। और यह वैसे भी एक optional language feature के रूप में propose किया गया है

    • Automated tests से risk को काफी हद तक कम किया जा सकता है, और fast startup के बदले यह tradeoff उचित है। Startup time सिर्फ “cosmetic” समस्या नहीं है। मैंने Django monolith में ऐसी स्थिति देखी है जहां सिर्फ कुछ भारी libraries की वजह से हर management command, test, और container reload पर 10–15 सेकंड इंतजार करना पड़ता था। Lazy import से defer करने पर बहुत बड़ा फर्क पड़ा

  • हम लोग explicit top-level imports पसंद करते हैं, क्योंकि इससे program start होते ही dependency problems सामने आ जाती हैं। Lazy import इस्तेमाल करने पर दिक्कत यह होगी कि कोई खास code path चलने पर (शायद कई घंटे या कई दिनों बाद) समस्या पता चले

    • उल्टा देखें तो, अगर सभी imports अपने-आप defer हों, तो छोटे कामों में pip की execution speed तुरंत तेज हो जाएगी
$ time pip install --disable-pip-version-check
ERROR: You must give at least one requirement to install (see "pip help install")

real  0m0.399s
user  0m0.360s
sys   0m0.041s

ज़्यादातर समय उन vendor modules को import और unload करने में जाता है जिनका वास्तव में इस्तेमाल भी नहीं होता (जैसे सिर्फ Requests से जुड़े लगभग 100 modules)। जांच करने पर पता चला कि कुल 500 से ज्यादा modules बेवजह import हो रहे हैं

  • समझ नहीं आता कि code generators top-level imports की जगह function के अंदर local imports डालने वाला code इतना क्यों बना रहे हैं। मैं उस pattern को recommend नहीं करना चाहूंगा, क्योंकि इससे module dependencies समझना मुश्किल हो जाता है और बाद में circular dependencies का जोखिम बढ़ता है

  • मैंने अभी PEP पूरा नहीं पढ़ा, लेकिन अगर command line flag या external tool से dependency validation किया जा सके तो अच्छा होगा, type hints के tooling जैसा कुछ

  • “हम लोग” से आपका मतलब ठीक-ठीक कौन है?

  • क्या यह ऐसी चीज़ नहीं है जिसे tests से cover किया जाना चाहिए?