PEP 810 – स्पष्ट lazy import
(pep-previews--4622.org.readthedocs.build)- 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 नए
lazykeyword के साथ 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-partylazy_loaderpackage मौजूद हैं, लेकिन वे सभी cases को cover नहीं करते या कोई एक standard अनुपस्थित है
PEP 810: explicit lazy import की शुरुआत
-
नया
lazysoft 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 (
*) सभी में निषिद्ध
- 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_CHECKINGconditional 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 में
lazykeyword जोड़कर, 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 टिप्पणियां
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.bazlazy होता है। शायद 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 तोड़ेगा और अनपेक्षित समस्याएं भी लाएगा।
importstatement में मूल रूप से 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 पर सीधा असर पड़ता है।importstatement लगभग हर वास्तविक 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 किया जाता औरimportsyntax बदलता ही नहीं। तब सिर्फ बड़ी 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 पेश है। आम तौर पर सिर्फ
importstatement कोwithblock में wrap करना होता है, इसलिए यह मौजूदा tools के साथ भी अच्छी तरह काम करता है। Debugging चाहिए तो आसानी से eager import पर switch किया जा सकता है। यह C extension के जरिए frame केf_builtinsको बदलकरimportlibhook से भी ज्यादा 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 किया जा सकता हैइस तरह meta path finder को wrap करने वाले wrapper से बदल दिया जाता है, जो loader को
LazyLoaderसे replace करता है।importचलने पर module नाम वास्तव में<class 'importlib.util._LazyModule'>से bind होता है, और attribute access होने पर असली module load होता है। प्रयोग कोड:लेकिन 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 चलने पर (शायद कई घंटे या कई दिनों बाद) समस्या पता चले
pipकी execution speed तुरंत तेज हो जाएगीज़्यादातर समय उन 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 किया जाना चाहिए?