51Testing软件测试论坛

标题: 浅谈lazy import 如何实现惰性导入? [打印本页]

作者: lsekfe    时间: 2022-10-19 13:34
标题: 浅谈lazy import 如何实现惰性导入?
如果你的 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 类复制到自己的项目中去即可。
  源代码如下:
  1. <font size="3"># Code copied from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/util/lazy_loader.py

  2.   """A LazyLoader class."""

  3.   from __future__ import absolute_import

  4.   from __future__ import division

  5.   from __future__ import print_function

  6.   import importlib

  7.   import types

  8.   class LazyLoader(types.ModuleType):

  9.     """Lazily import a module, mainly to avoid pulling in large dependencies.

  10.     `contrib`, and `ffmpeg` are examples of modules that are large and not always

  11.     needed, and this allows them to only be loaded when they are used.

  12.     """

  13.     # The lint error here is incorrect.

  14.     def __init__(self, local_name, parent_module_globals, name):  # pylint: disable=super-on-old-class

  15.       self._local_name = local_name

  16.       self._parent_module_globals = parent_module_globals

  17.       super(LazyLoader, self).__init__(name)

  18.     def _load(self):

  19.       # Import the target module and insert it into the parent's namespace

  20.       module = importlib.import_module(self.__name__)

  21.       self._parent_module_globals[self._local_name] = module

  22.       # Update this object's dict so that if someone keeps a reference to the

  23.       #   LazyLoader, lookups are efficient (__getattr__ is only called on lookups

  24.       #   that fail).

  25.       self.__dict__.update(module.__dict__)

  26.       return module

  27.     def __getattr__(self, item):

  28.       module = self._load()

  29.       return getattr(module, item)

  30.     def __dir__(self):

  31.       module = self._load()

  32.       return dir(module)</font>
复制代码
代码说明:
  类 LazyLoader 继承自 types.ModuleType,初始化函数确保惰性模块将像真正的模块一样正确添加到全局变量中,只要真正用到模块的时候,也就是执行 __getattr__ 或 __dir__ 时,才会真正的 import 实际模块,更新全局变量以指向实际模块,并且将其所有状态(__dict__)更新为实际模块的状态,以便对延迟加载的引用,加载模块不需要每次访问都经过加载过程。
  代码使用:
  正常情况下我们这样导入模块:

  1. <font size="3">import tensorflow.contrib as contrib</font>
复制代码
  其对应的惰性导入版本如下:

  1. <font size="3">contrib = LazyLoader('contrib', globals(), 'tensorflow.contrib')</font>
复制代码
PEP0690 建议的做法
  PEP0690 的提案是在编译器( C 代码)层面实现,这样性能会更好。其使用方法有两种。
  其一
  一种方式是执行 Python 脚本时加入 -L 参数,比如有两个文件 spam.py 内容如下:

  1. <font size="3"> import time

  2.   time.sleep(10)

  3.   print("spam loaded")</font>
复制代码
egg.py 内容如下:
  1. <font size="3">import spam

  2.   print("imports done")</font>
复制代码
正常导入情况下,会等 10 秒后先打印 "spam loaded",然后打印 "imports done",当执行 python -L eggs.py 时,spam 模块永远不会导入,应用 spam 模块压根就没有用到。如果 egg.py 内容如下:
  1. <font size="3"> import spam

  2.   print("imports done")

  3.   spam</font>
复制代码
当执行 python -L eggs.py 时会先打印 "imports done",10 秒之后打印 "spam loaded")。
  其二
  另一种方式是调用标准库 importlib 的方法:

  1. <font size="3">import importlib

  2.   importlib.set_lazy_imports(True)</font>
复制代码
如果某些模块不能懒加载,需要排除,可以这样:
  1. <font size="3"> import importlib

  2.   importlib.set_lazy_imports(True,excluding=["one.mod", "another"])</font>
复制代码
还可以这样:
  1. <font size="3"> from importlib import eager_imports

  2.   with eager_imports():

  3.       import foo

  4.       import bar</font>
复制代码
最后的话
  经过专业人士在真实的 Python 命令行程序上做测试,应用惰性导入后,可以使启动时间提高 70%,内存使用减少 40%,非常可观了。







欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2