LWN: Comments on "Lazy imports for Python" https://lwn.net/Articles/907226/ This is a special feed containing comments posted to the individual LWN article titled "Lazy imports for Python". en-us Sat, 01 Nov 2025 09:12:28 +0000 Sat, 01 Nov 2025 09:12:28 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net Lazy imports for Python https://lwn.net/Articles/908101/ https://lwn.net/Articles/908101/ smurf <div class="FormattedComment"> The idea behind lazy imports is to not access the file system at all until the module is actually used. As soon as you go off and search + read a .pyc or .pyo file header, the advantage of not going through the whole file system dance is gone (did you ever look at how many file system accesses Python needs to do to locate a module?) and you might as well import the thing.<br> <p> <p> </div> Thu, 15 Sep 2022 08:54:19 +0000 Lazy imports for Python https://lwn.net/Articles/907919/ https://lwn.net/Articles/907919/ arvidma <div class="FormattedComment"> All of that recursive loading and parsing happens already today, since loading is eager. Putting a tag in each pyc file to indicate if it can be lazy loaded shouldn&#x27;t need to add much cost (each pyc is touched twice instead of once, but they would all be in the page cache and that pyc-update ought to be comparatively cheaper than the parsing and compiling of the py-files).<br> <p> This way, you only pay the cost once and any subsequent runs can take the fast path.<br> <p> </div> Wed, 14 Sep 2022 06:39:25 +0000 Lazy imports for Python https://lwn.net/Articles/907899/ https://lwn.net/Articles/907899/ xnox <div class="FormattedComment"> In python, import foo not only adds foo module to global namespace and allows accessing variables, functions, and classes that foo declares but it also typically executes any arbitrary code inside __init__ of said module which can have global side effects. For example establish network connections, migrating settings, writing out files or even simply setting process global environment variable.<br> <p> import setup<br> import daemon<br> daemon.start()<br> <p> Can fail, if setup has a side effect of exporting environment variables which start of Daemon expected to be set.<br> <p> Same issues as shell source command, which also is hard to make &quot;lazy&quot;.<br> </div> Wed, 14 Sep 2022 00:02:17 +0000 Lazy imports for Python https://lwn.net/Articles/907725/ https://lwn.net/Articles/907725/ NYKevin <div class="FormattedComment"> Also, if you&#x27;re trying to *catch* the exception, then it will automatically be eager because it&#x27;s not a top-level import anymore (at an absolute minimum, it&#x27;s inside of a try block).<br> </div> Sun, 11 Sep 2022 19:24:05 +0000 Lazy imports for Python https://lwn.net/Articles/907709/ https://lwn.net/Articles/907709/ smurf <div class="FormattedComment"> Python itself is not stringly-typed, but the command line sure is.<br> </div> Sun, 11 Sep 2022 03:18:36 +0000 Lazy imports for Python https://lwn.net/Articles/907699/ https://lwn.net/Articles/907699/ NYKevin <div class="FormattedComment"> Well, yeah. If you force everything to use a stringly-typed interface, life sucks. But this article is about Python, which is not stringly-typed. I object to calling this a &quot;workaround&quot; - it&#x27;s the correct way to design the API when you have real first-class modules available in your type system.<br> </div> Sat, 10 Sep 2022 20:03:51 +0000 Lazy imports for Python https://lwn.net/Articles/907688/ https://lwn.net/Articles/907688/ NYKevin <div class="FormattedComment"> The concern is that, possibly, the imports are done by the application and not by the library itself. In other words, we might have something like this:<br> <p> main.py:<br> <p> import primary_library<br> import some_plugin<br> import another_plugin<br> <p> Each of the plugin files imports primary_library and then calls some magic init function from there, but the actual API is primary_library (i.e. you just import some_plugin for the magic init side-effect and not to actually use it directly). The plugins are third-party code. You can&#x27;t just fix it in primary_library, because primary_library doesn&#x27;t know about the plugins and can&#x27;t find and init them by itself, even if the application does call primary_library.init().<br> <p> The reasonable solution is to require the application to call primary_library.init(some_plugin) and explicitly say which plugin(s) to init. But that might be a more significant refactoring job. OTOH, explicit is better than implicit, and this is IMHO a superior coding style to just &quot;write import x and magic happens.&quot;<br> </div> Sat, 10 Sep 2022 16:57:27 +0000 Lazy imports for Python https://lwn.net/Articles/907594/ https://lwn.net/Articles/907594/ smurf <div class="FormattedComment"> Forgot to add: I just traced two dbus command line tools I&#x27;m using around here. They have comparable startup times (that is, way too long), but the number of modules they load without actually needing them is negligible.<br> </div> Fri, 09 Sep 2022 19:14:19 +0000 Lazy imports for Python https://lwn.net/Articles/907593/ https://lwn.net/Articles/907593/ smurf <div class="FormattedComment"> Mmh. I&#x27;d assume that half that time is spent waiting for the introspection XML, and much of the other half might well be loading etree and decoding the thing.<br> <p> DBus is nice, but its metadata system could have used a somewhat simpler data model.<br> </div> Fri, 09 Sep 2022 19:12:03 +0000 Lazy imports for Python https://lwn.net/Articles/907592/ https://lwn.net/Articles/907592/ smurf <div class="FormattedComment"> This is why you have tests.<br> <p> The same kind of argument has been used 20 years ago when libc started to use lazy loading. The solution was a loader flag that loaded eagerly and reported when something didn&#x27;t resolve. On Python, the equivalent solution might well be a dedicated import checker.<br> <p> Fortunately, tools like that do exist already. :-P<br> </div> Fri, 09 Sep 2022 19:05:47 +0000 Lazy imports for Python https://lwn.net/Articles/907590/ https://lwn.net/Articles/907590/ smurf <div class="FormattedComment"> Your workaround is fine when using an object-oriented design. However you can&#x27;t use it e.g. with command-line plugins. When I call &quot;mount -t xfs&quot; I expect the mount command to find the XFS plugin somehow, and if the registration happens solely by import that won&#x27;t work.<br> <p> The fix for this is to park each imported part in a separate module, then use the filename to load it.<br> <p> </div> Fri, 09 Sep 2022 19:00:16 +0000 Lazy imports for Python https://lwn.net/Articles/907575/ https://lwn.net/Articles/907575/ azumanga <div class="FormattedComment"> Some libraries I work on check for required external binaries and libraries at import, and throw an exception if anything is broken. That can be caught, or ignored if you just want the program to stop.<br> <p> Now that exception will be thrown in whatever function the library is first used in. That may well be surprising, and could cause issues I imagine.<br> </div> Fri, 09 Sep 2022 13:36:18 +0000 Lazy imports for Python https://lwn.net/Articles/907554/ https://lwn.net/Articles/907554/ georgm <pre> # time firewall-cmd --state running real 0m1.675s user 0m0.861s sys 0m0.105s </pre> for a simple DBUS call...<br><br> Hopefully this will benefit from such a change Fri, 09 Sep 2022 10:28:09 +0000 Lazy imports for Python https://lwn.net/Articles/907549/ https://lwn.net/Articles/907549/ farnz <p>Or, on the assumption that you're willing to do work to support lazy loading, you move all of the global code bar imports into the init function, and then have the last line of your module be <tt>init()</tt>. This is a bigger refactor, but it means that old users get the behaviour they expect (import runs the code), and new users can do a lazy load followed by a call to init to get the same behaviour. Fri, 09 Sep 2022 08:15:05 +0000 Lazy imports for Python https://lwn.net/Articles/907528/ https://lwn.net/Articles/907528/ NYKevin <p> This either doesn't work, or it's basically useless. <p> foo.py: <pre> import bar </pre> <p> bar.py: <pre> print("Side effect!") </pre> <p> main.py: <pre> import foo </pre> <p> Now, if you make main.py lazily import foo.py, then the side effects of bar.py will stop happening, which main might have accidentally relied on. So you would need to treat import statements as "statements other than declarations" - which effectively means that almost any nontrivial module will be eagerly imported. Or, alternatively, you have to recursively trace through all of the modules in the entire dependency tree and check each one for this lazy import flag - which is still pretty expensive and doesn't really save you all that much (consider the disk seeks!). Thu, 08 Sep 2022 20:30:33 +0000 Lazy imports for Python https://lwn.net/Articles/907527/ https://lwn.net/Articles/907527/ NYKevin <div class="FormattedComment"> You could have init() find the relevant code and eagerly import it (your imports would not be at the top level, so they would be eager regardless of the application&#x27;s preferences). But that might make a bad design worse, depending on how difficult it is to &quot;find&quot; the relevant code.<br> </div> Thu, 08 Sep 2022 20:24:21 +0000 Lazy imports for Python https://lwn.net/Articles/907518/ https://lwn.net/Articles/907518/ mathstuf <div class="FormattedComment"> <font class="QuotedText">&gt; Having said that, you might consider moving that logic into an init() function that the application can call into</font><br> <p> There&#x27;s no function for `init` to bind; the relevant code is all stored in global initializers (again, not the design I&#x27;d prefer, but it&#x27;s what I have).<br> </div> Thu, 08 Sep 2022 17:51:53 +0000 Lazy imports for Python https://lwn.net/Articles/907515/ https://lwn.net/Articles/907515/ NYKevin <div class="FormattedComment"> No. You can just tell end users &quot;sorry, we don&#x27;t have plans to support that&quot; and then dupe all subsequent requests against that one. Just because people send you feature requests, it doesn&#x27;t mean you have to act on them.<br> <p> Having said that, you might consider moving that logic into an init() function that the application can call into, if possible. Then, when someone inevitably asks for lazy loading support, tell them &quot;Sure, we support enable lazy loading, just make sure you call this init() function.&quot; Since they&#x27;re already making a code change anyway (to enable lazy loading), they should not have a backcompat objection to that.<br> </div> Thu, 08 Sep 2022 16:51:34 +0000 Lazy imports for Python https://lwn.net/Articles/907474/ https://lwn.net/Articles/907474/ mathstuf <div class="FormattedComment"> <font class="QuotedText">&gt; You can&#x27;t possibly know what &quot;every application&quot; is going to depend on (you might be a dependency of a dependency of a dependency that the application barely calls into at all), so I&#x27;m skeptical that this is a real thing.</font><br> <p> The library is VTK. What happens is that there are &quot;object factory&quot; types that can give you back a subclass if they are &quot;registered&quot;. So you ask for a `vtkOpenGLRenderWindow` and you get back one that actually does X or Cocoa without having to know which you want at every call site. There are a variety of classes that do this.<br> <p> In C++, there&#x27;s a mechanism to see &quot;oh, you included the base class&quot; and the buildsystem injects &quot;and this TU knows about overloads in library X and Y, so call X and Y&#x27;s registration mechanisms&quot; through that include through some injected global static ctors that ensure registration happens. Due to the way various platforms actually work, this needs to be done in every TU to be reliable[1]. Python accesses this by importing the module that has overloads and then it works &quot;by magic&quot; due to the module initializer hooking this mechanism (since the wrappers need to include the relevant headers).<br> <p> <font class="QuotedText">&gt; because import-time side effects are evil.</font><br> <p> I&#x27;m not saying they&#x27;re not, but alas C++ gives such little control over global initializers, that for some solutions, there&#x27;s no reasonable alternative. As much as *I&#x27;d* like to make these things scoped and explicit, getting everyone to update is beyond the software process capital I&#x27;m willing to spend (as if I had that much anyways).<br> <p> [1] For example, static builds on macOS (at one point?) only call object-local global initializers when something from the object is actually used. There&#x27;s no place to put a library ctor at the static-library level, so you just need to inject &quot;everywhere&quot; to be reliable.<br> </div> Thu, 08 Sep 2022 13:12:15 +0000 Lazy imports for Python https://lwn.net/Articles/907473/ https://lwn.net/Articles/907473/ mathstuf <div class="FormattedComment"> While true, I think it&#x27;d be hard to argue that such APIs don&#x27;t exist today. Is Python effectively forcing[1] these APIs to be redesigned with this feature?<br> <p> For the project I know that will be affected by this, it is all in the library ctor stuff that stuff gets set up; there&#x27;s no API *to* call. Again, not the best design, but doing it explicitly would make &quot;everyone&quot; unhappy because of how much work it ends up doing for users.<br> <p> [1] Via feature request pressure to &quot;support lazy loading&quot; over time.<br> </div> Thu, 08 Sep 2022 13:03:33 +0000 Lazy imports for Python https://lwn.net/Articles/907472/ https://lwn.net/Articles/907472/ mathstuf <div class="FormattedComment"> I suspect that avoiding even finding that bytecode is part of the expected savings here. However, maybe modules could have this mark to at least avoid recursively loading all of the needed modules immediately. That might be a nicer compromise point.<br> </div> Thu, 08 Sep 2022 13:00:07 +0000 Lazy imports for Python https://lwn.net/Articles/907466/ https://lwn.net/Articles/907466/ LtWorf <div class="FormattedComment"> As a (mostly) python developer, I&#x27;d be happy to see this go in.<br> <p> At work we have an internal python tool that to do --version loads 604 files.<br> <p> This is mostly due to type annotations, that force the imports just to declare a function that uses a certain type.<br> <p> Importing modules before using them is annoying and requires a lot of discipline, but is only a partial solution<br> <p> As developer of typedload (like pydantic but better) I am not sure this would help me much. The code imports a bunch of modules to support those types, but the types are referenced so the import happens anyway. I&#x27;d have to do a lot of trickery to only reference them when they are actually needed, but that would probably mean that the library would no longer work with cython.<br> </div> Thu, 08 Sep 2022 10:17:51 +0000 Lazy imports for Python https://lwn.net/Articles/907465/ https://lwn.net/Articles/907465/ taladar <div class="FormattedComment"> As if Python didn&#x27;t already have enough ways to fail in interesting and exciting ways at runtime when the installed libraries don&#x27;t match what it expected.<br> </div> Thu, 08 Sep 2022 08:30:31 +0000 Lazy imports for Python https://lwn.net/Articles/907464/ https://lwn.net/Articles/907464/ josh <div class="FormattedComment"> I wonder if this could autodetect correctness? When byte-compiling a Python module, look for either a lack of any statements at top level other than undecorated declarations (or perhaps also decorators that have an annotation opting in), or an explicit opt in, and emit a flag in the bytecode (or a flag file if that&#x27;s easier to check for). If that flag exists, do lazy loading for that module.<br> </div> Thu, 08 Sep 2022 08:20:24 +0000 Lazy imports for Python https://lwn.net/Articles/907461/ https://lwn.net/Articles/907461/ yaap <div class="FormattedComment"> Interesting, it looks like a distant cousin of Emacs autoload package support: different in the details, but same goal to reduce start-up time. In Emacs an elisp package can declare some public commands as autoloaded, they will be implemented as stubs that load the full package on first use (and the stubs are replaced by the full commands).<br> <p> </div> Thu, 08 Sep 2022 07:40:01 +0000 Lazy imports for Python https://lwn.net/Articles/907434/ https://lwn.net/Articles/907434/ NYKevin <div class="FormattedComment"> <font class="QuotedText">&gt; The PEP says that laziness will be per-module.</font><br> <p> This is incorrect, I misread <a href="https://peps.python.org/pep-0690/#deep-eager-imports-override">https://peps.python.org/pep-0690/#deep-eager-imports-over...</a>. My bad.<br> </div> Wed, 07 Sep 2022 22:06:11 +0000 Lazy imports for Python https://lwn.net/Articles/907433/ https://lwn.net/Articles/907433/ NYKevin <div class="FormattedComment"> IMHO this is a result of poor API design. It should really look like this:<br> <p> import mount # Filesystem support<br> import mount_xfs # XFS support<br> <p> mount.mount(type=mount_xfs)<br> <p> Or maybe you use type=mount_xfs.mount_type or something like that, but you can just pass the whole module if you want to. Python doesn&#x27;t care. They&#x27;re all PyObject* under the hood.<br> </div> Wed, 07 Sep 2022 21:59:55 +0000 Lazy imports for Python https://lwn.net/Articles/907431/ https://lwn.net/Articles/907431/ NYKevin <div class="FormattedComment"> <font class="QuotedText">&gt; - setting up lazy imports only works from a `__name__ == &#x27;__main__&#x27;` context; and</font><br> <p> The PEP says that laziness will be per-module. If you call set_lazy_imports() in foo.py, and bar.py does not call set_lazy_imports(), then all of the imports in bar.py will be eagerly evaluated (even if foo.py lazily imports those same modules first - what happens is that bar.py&#x27;s import gets eagerly evaluated, and then when foo.py&#x27;s lazy import eventually needs to be evaluated, the import machinery resolves it back to the same module that bar.py already imported, just like it would if both imports were eager). IMHO if some library wants to use lazy imports as part of an internal implementation detail that application code will never see, then we might as well allow it.<br> <p> <font class="QuotedText">&gt; - modules/packages have a way to say &quot;I will never support lazy loading&quot; to avoid having every single consumer have to add it to their exclusion list.</font><br> <p> This basically translates to &quot;I&#x27;m deliberately causing nontrivial import-time side effects that every application will necessarily depend on.&quot; You can&#x27;t possibly know what &quot;every application&quot; is going to depend on (you might be a dependency of a dependency of a dependency that the application barely calls into at all), so I&#x27;m skeptical that this is a real thing. But regardless, I don&#x27;t think Python should encourage this sort of chicanery, or go out of its way to support it, because import-time side effects are evil.<br> </div> Wed, 07 Sep 2022 21:57:51 +0000 Lazy imports for Python https://lwn.net/Articles/907432/ https://lwn.net/Articles/907432/ mathstuf <div class="FormattedComment"> Any module which uses top-level code to talk to other modules and do any global state setup. For a kernel-flavored example:<br> <p> ```python<br> import mount # Filesystem support<br> import mount_xfs # Enable XFS support<br> <p> mount.mount(type=&#x27;xfs&#x27;) # Oops, XFS isn&#x27;t actually loaded because the `mount_xfs` name wasn&#x27;t tickled.<br> ```<br> </div> Wed, 07 Sep 2022 21:57:30 +0000 Lazy imports for Python https://lwn.net/Articles/907430/ https://lwn.net/Articles/907430/ willy <div class="FormattedComment"> I&#x27;m not particularly python-savvy (but I enjoy the insights into the evolution of the language). What are the kinds of things that can break with lazy loading?<br> </div> Wed, 07 Sep 2022 21:40:06 +0000 Lazy imports for Python https://lwn.net/Articles/907426/ https://lwn.net/Articles/907426/ mathstuf <div class="FormattedComment"> Seems mostly reasonable. However, there are two things I&#x27;d like to see:<br> <p> - setting up lazy imports only works from a `__name__ == &#x27;__main__&#x27;` context; and<br> - modules/packages have a way to say &quot;I will never support lazy loading&quot; to avoid having every single consumer have to add it to their exclusion list.<br> </div> Wed, 07 Sep 2022 19:46:54 +0000