如何实现 Python 的惰性导入-lazy import

发表于:2022-10-19 10:23

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:somenzz    来源:Python七号

  如果你的 Python 程序程序有大量的 import,而且启动非常慢,那么你应该尝试懒导入,本文分享一种实现惰性导入的一种方法。虽然 PEP0690 已经提案让 Python 编译器(-L) 或者标准库加入这个功能,但目前的 Python 版本还未实现。
  众所周知,Python 应用程序在执行用户的实际操作之前,会执行 import 操作,不同的模块可能来自不同的位置,某些模块的运行可能非常耗时,某些模块可能根本不会被用户调用,因此很多模块的导入纯粹是浪费时间。
  因此我们需要惰性导入,当应用惰性导入时,运行 import foo 仅仅会把名字 foo 添加到全局的全名空间(globals())中作为一个懒引用(lazy reference),编译器遇到任何访问 foo 的代码时才会执行真正的 import 操作。类似的,from foo import bar 会把 bar 添加到命名空间,当遇到调用 bar 的代码时,就把 foo 导入。
  写代码实现
  那怎么写代码实现呢?其实不必写代码实现,已经有项目实现了懒导入功能,那就是 TensorFlow,它的代码并没有任何三方库依赖,我把它放到这里,以后大家需要懒导入的时候直接把 LazyLoader 类复制到自己的项目中去即可。
  源代码如下:
  # Code copied from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/util/lazy_loader.py
  """A LazyLoader class."""
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  import importlib
  import types
  class LazyLoader(types.ModuleType):
    """Lazily import a module, mainly to avoid pulling in large dependencies.
    `contrib`, and `ffmpeg` are examples of modules that are large and not always
    needed, and this allows them to only be loaded when they are used.
    """
    # The lint error here is incorrect.
    def __init__(self, local_name, parent_module_globals, name):  # pylint: disable=super-on-old-class
      self._local_name = local_name
      self._parent_module_globals = parent_module_globals
      super(LazyLoader, self).__init__(name)
    def _load(self):
      # Import the target module and insert it into the parent's namespace
      module = importlib.import_module(self.__name__)
      self._parent_module_globals[self._local_name] = module
      # Update this object's dict so that if someone keeps a reference to the
      #   LazyLoader, lookups are efficient (__getattr__ is only called on lookups
      #   that fail).
      self.__dict__.update(module.__dict__)
      return module
    def __getattr__(self, item):
      module = self._load()
      return getattr(module, item)
    def __dir__(self):
      module = self._load()
      return dir(module)
  代码说明:
  类 LazyLoader 继承自 types.ModuleType,初始化函数确保惰性模块将像真正的模块一样正确添加到全局变量中,只要真正用到模块的时候,也就是执行 __getattr__ 或 __dir__ 时,才会真正的 import 实际模块,更新全局变量以指向实际模块,并且将其所有状态(__dict__)更新为实际模块的状态,以便对延迟加载的引用,加载模块不需要每次访问都经过加载过程。
  代码使用:
  正常情况下我们这样导入模块:
  import tensorflow.contrib as contrib
  其对应的惰性导入版本如下:
  contrib = LazyLoader('contrib', globals(), 'tensorflow.contrib')
  PEP0690 建议的做法
  PEP0690 的提案是在编译器( C 代码)层面实现,这样性能会更好。其使用方法有两种。
  其一
  一种方式是执行 Python 脚本时加入 -L 参数,比如有两个文件 spam.py 内容如下:
  import time
  time.sleep(10)
  print("spam loaded")
  egg.py 内容如下:
  import spam
  print("imports done")
  正常导入情况下,会等 10 秒后先打印 "spam loaded",然后打印 "imports done",当执行 python -L eggs.py 时,spam 模块永远不会导入,应用 spam 模块压根就没有用到。如果 egg.py 内容如下:
  import spam
  print("imports done")
  spam
  当执行 python -L eggs.py 时会先打印 "imports done",10 秒之后打印 "spam loaded")。
  其二
  另一种方式是调用标准库 importlib 的方法:
  import importlib 
  importlib.set_lazy_imports(True)
  如果某些模块不能懒加载,需要排除,可以这样:
  import importlib 
  importlib.set_lazy_imports(True,excluding=["one.mod", "another"])
  还可以这样:
  from importlib import eager_imports
  with eager_imports():
      import foo
      import bar
  最后的话
  经过专业人士在真实的 Python 命令行程序上做测试,应用惰性导入后,可以使启动时间提高 70%,内存使用减少 40%,非常可观了。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号