懒加载特性

在 v1.0.41 后,本项目提供了 懒加载 特性,可以将模块和模块内常用方法或类包装为 LazyModuleLazyCallable,只在实际访问相应模块或类/方法时,才会加载

该特性可以显著减少模块导入时间

使用场景

例如有如下目录结构的项目

../_images/lazy_import_demo.png

其中 fake_module 内容如下

import pandas as pd
from deepfos.api.base import RootAPI

class F(RootAPI):
    pass

从其 import 的内容可以看出,为了导入 Class F ,将先导入 pandasdeepfos.api.base

假如 main.py 中只有如下代码

from demo.fake_pkg.fake_module import F

profile耗时倒序统计

         1242642 function calls (1223393 primitive calls) in 2.099 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    754/1    0.003    0.000    2.100    2.100 {built-in method builtins.exec}
    935/1    0.006    0.000    2.100    2.100 <frozen importlib._bootstrap>:1002(_find_and_load)
    931/1    0.004    0.000    2.100    2.100 <frozen importlib._bootstrap>:967(_find_and_load_unlocked)
    879/3    0.004    0.000    2.098    0.699 <frozen importlib._bootstrap>:659(_load_unlocked)
   1187/3    0.001    0.000    2.098    0.699 <frozen importlib._bootstrap>:220(_call_with_frames_removed)
    745/3    0.003    0.000    2.098    0.699 <frozen importlib._bootstrap_external>:844(exec_module)
        1    0.000    0.000    2.095    2.095 ...\demo\fake_pkg\fake_module.py:1(<module>)
   530/76    0.001    0.000    1.497    0.020 {built-in method builtins.__import__}
        1    0.000    0.000    1.087    1.087 ...\venv\lib\site-packages\pandas\__init__.py:3(<module>)
  829/480    0.001    0.000    0.709    0.001 <frozen importlib._bootstrap>:1033(_handle_fromlist)
  924/788    0.010    0.000    0.636    0.001 <frozen importlib._bootstrap>:901(_find_spec)
      745    0.009    0.000    0.508    0.001 <frozen importlib._bootstrap_external>:916(get_code)
        1    0.000    0.000    0.380    0.380 ...\deepfos\api\base.py:1(<module>)
        ......
        1    0.000    0.000    0.191    0.191 ...\venv\lib\site-packages\loguru\__init__.py:1(<module>)
        1    0.000    0.000    0.175    0.175 ...\venv\lib\site-packages\loguru\_logger.py:1(<module>)
        ......
        1    0.000    0.000    0.144    0.144 ...\venv\lib\site-packages\requests\__init__.py:8(<module>)
        ......

通过profile可得知,这些第三方包(包括deepfos)的导入耗时总和在1.5s以上,而代码本身并没有实际逻辑,导入的模块是未被使用的

而在引入了懒加载特性后,则可在 fake_pkg 的__init__.py文件中,使用 lazy_callable 方法,将 fake_module 下的 Class F 替换为懒加载的Class

from deepfos.lazy_import import lazy_callable

F = lazy_callable('demo.fake_pkg.fake_module', 'F')

main.py 作相应更新

from demo.fake_pkg import F

则上述 main.py 的profile将变为如下

         295750 function calls (289263 primitive calls) in 0.522 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    247/1    0.001    0.000    0.522    0.522 {built-in method builtins.exec}
    294/1    0.002    0.000    0.522    0.522 <frozen importlib._bootstrap>:1002(_find_and_load)
    293/1    0.001    0.000    0.522    0.522 <frozen importlib._bootstrap>:967(_find_and_load_unlocked)
    362/2    0.000    0.000    0.521    0.261 <frozen importlib._bootstrap>:220(_call_with_frames_removed)
    279/2    0.001    0.000    0.521    0.261 <frozen importlib._bootstrap>:659(_load_unlocked)
    245/2    0.001    0.000    0.521    0.261 <frozen importlib._bootstrap_external>:844(exec_module)
        1    0.000    0.000    0.521    0.521 ...\demo\fake_pkg\__init__.py:1(<module>)
     67/4    0.000    0.000    0.518    0.130 {built-in method builtins.__import__}
        1    0.000    0.000    0.372    0.372 ...\deepfos\__init__.py:2(<module>)
        1    0.000    0.000    0.235    0.235 ...\deepfos\_version.py:580(get_versions)
        1    0.000    0.000    0.235    0.235 ...\deepfos\_version.py:222(git_pieces_from_vcs)
        5    0.000    0.000    0.234    0.047 ...\deepfos\_version.py:71(run_command)
        5    0.000    0.000    0.150    0.030 ...\Python39\lib\subprocess.py:1090(communicate)
        1    0.000    0.000    0.148    0.148 ...\deepfos\lazy_import.py:1(<module>)
        1    0.000    0.000    0.146    0.146 ...\deepfos\lib\constant.py:1(<module>)
        1    0.000    0.000    0.143    0.143 ...\venv\lib\site-packages\requests\__init__.py:8(<module>)
        1    0.000    0.000    0.136    0.136 ...\deepfos\options.py:1(<module>)
      249    0.131    0.001    0.131    0.001 {method 'read' of '_io.BufferedReader' objects}
        1    0.000    0.000    0.125    0.125 ...\venv\lib\site-packages\loguru\__init__.py:1(<module>)
        1    0.000    0.000    0.116    0.116 ...\venv\lib\site-packages\loguru\_logger.py:1(<module>)
    ......

虽然 deepfos.lazy_import 的引入增加了少许来自deepfos的__init__.py和 deepfos.lazy_import 自身需要导入的模块及相应逻辑,但省去了耗时最高的 pandas 包和 deepfos.api.base 模块的导入时间,使得总体耗时减少了1.577s

lazy_module

该方法将提供根据module名包装的 LazyModule实例 ,对该module的import操作(即 importlib.import )将在访问module内的成员时才发生。

>>> import sys
>>> from deepfos.lazy_import import lazy_module
>>> ccc = lazy_module("aaa.bbb.ccc")
# 当前命名空间可访问到的ccc等效于
# from aaa.bbb import ccc中的ccc
# 此时,aaa.bbb.ccc不在sys.modules中
>>> 'aaa.bbb.ccc' in sys.modules
False
# 在试图访问ccc模块内的D成员时
# 才会试图导入aaa, aaa.bbb, aaa.bbb.ccc
# 由于aaa不存在
# 此处将报ModuleNotFoundError
>>> print(ccc.D)
Traceback (most recent call last):
  ......
ModuleNotFoundError: No module named 'aaa'

警告

预期lazy_module的module_name是模块名,而非包(package)名,这意味着,如果设置了lazy_module,

将其当作包来访问其下module的操作将导致 AttributeError

例如在demo的__init__.py中如下编写

from deepfos.lazy_import import lazy_module

fake_pkg = lazy_module('demo.fake_pkg')

试图在demo的同级目录访问 fake_pkg 下的 fake_module

from demo import fake_pkg

fake_pkg.fake_module

将报如下错

AttributeError: module 'demo.fake_pkg' has no attribute 'fake_module'

lazy_callable

相较于懒加载模块,更常见的实际使用场景是引入某模块内的方法/类时,希望避免对一整个模块的导入,因此基于 lazy_module 方法,提供了lazy_callable方法

该方法将module名对应的module包装为 LazyModule 后,为其增加与names(多个方法/类名)同名的 LazyCallable 成员,对其模块内方法/类的调用将变为对 LazyCallable 的调用

from deepfos.lazy_import import lazy_callable

# lazy_callable返回的是LazyCallable的tuple
A, = lazy_callable("aaa.bbb.ccc", "A")

# 指定多个LazyCallable
B, C, D, E = lazy_callable("aaa.bbb.ccc", "B", "C", "D", "E")

Deepfos项目的懒加载模块和方法/类

目前本项目中的 core , db , element 包都已支持懒加载特性,对其中的懒加载方法/类,可以直接从 core , db , element 包导入,得到的即为懒加载后的 LazyCallable 对象,而不涉及其所在module文件的导入

from deepfos.element import Datatable

# 此处的Datatable是LazyCallable包装后的Datatable
ele = Datatable('test')

deepfos.core

  • cube
    • SysCube

    • Cube

    • as_function_node

  • dimension
    • DimMember

    • SysDimension

    • read_expr

    • Dimension

    • DimExprAnalysor

    • ElementDimension

  • logictable
    • SQLCondition

    • BaseTable

    • MetaTable

    • TreeRenderer

deepfos.db

  • mysql
    • MySQLClient

    • AsyncMySQLClient

  • clickhouse
    • ClickHouseClient

    • AsyncClickHouseClient

  • oracle
    • OracleClient

    • AsyncOracleClient

    • OracleDFSQLConvertor

  • sqlserver
    • SQLServerClient

    • AsyncSQLServerClient

  • kingbase
    • KingBaseClient

    • AsyncKingBaseClient

  • gauss
    • GaussClient

    • AsyncGaussClient

  • dameng
    • DaMengClient

    • AsyncDaMengClient

  • postgresql
    • PostgreSQLClient

    • AsyncPostgreSQLClient

  • deepengine
    • DeepEngineClient

    • AsyncDeepEngineClient

deepfos.element

  • accounting
    • AccountingEngines

    • BillEngines

    • CallbackInfo

  • apvlprocess
    • ApprovalProcess

    • AsyncApprovalProcess

  • bizmodel
    • AsyncBusinessModel

    • BusinessModel

    • CopyConfig

  • datatable
    • AsyncDataTableMySQL

    • AsyncDataTableClickHouse

    • AsyncDataTableOracle

    • AsyncDataTableSQLServer

    • AsyncDataTableKingBase

    • AsyncDataTableGauss

    • AsyncDataTableDaMeng

    • AsyncDataTablePostgreSQL

    • AsyncDataTableDeepEngine

    • Datatable

    • DataTableMySQL

    • DataTableClickHouse

    • DataTableOracle

    • DataTableSQLServer

    • DataTableKingBase

    • DataTableGauss

    • DataTableDaMeng

    • DataTablePostgreSQL

    • DataTableDeepEngine

  • dimension
    • AsyncDimension

    • Dimension

    • Strategy

  • fact_table
    • AsyncFactTable

    • FactTable

  • finmodel
    • AsyncFinancialCube

    • FinancialCube

  • journal_template
    • AsyncJournalTemplate

    • JournalTemplate

    • FullPostingParameter

  • rolestrategy
    • AsyncRoleStrategy

    • RoleStrategy

  • smartlist
    • AsyncSmartList

    • SmartList

  • variable
    • AsyncVariable

    • Variable