Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

Pythonモジュールの遅延import

- はじめに -

この記事は、Xonsh Advent Calendar 2017 - Qiita 18日目の記事です。

Pythonのmoduleのloadを実際に利用する前に遅延してやろうというTipsです。

加えて、xonshのxonshrcに記載する事で、xonshの起動も早くしようという話を書いています。


アジェンダ

 

- 遅延importを実現する -

遅延させて、使いたい時にpythonスクリプトをロードしたりするには大抵importlibを使います。

そこでimportlibでxonshの起動爆速化を狙おうとしていた所、同僚に「lazyasdっていうのがあるよ」と言われて調べたら大体それで出来たので、これで良いやという感じでした。

以下には一応どちらも記述しています。

 

importlibで動的ロード -

Pythonでモジュールをスクリプト上で動的にロードするにはimportlibを使います。

31.5. importlib — The implementation of import — Python 3.6.4 documentation

import importlib

os = importlib.import_module("os")
print(os.listdir("."))

machineryを使えばimportのタイミングをhookできます。
https://docs.python.org/3.6/library/importlib.html#module-importlib.machinery

遅延してロードさせる場合は、Python3.5にて追加されたimportlib.util.LazyLoaderを使うと良いです。
31.5. importlib — The implementation of import — Python 3.6.4 documentation


参考:Python: モジュールを動的にロードする - CUBE SUGAR CONTAINER


 

lazyasdを使った遅延import

同僚が「xonsh使ってるならxonsh.lazyasdの中にLazyObjectやBackgroundModuleLoaderがあるのでそれ使うと良いよ」と教えてくれました。

Lazy & Self-destructive Objects (xonsh.lazyasd) — xonsh 0.6.0.dev151 documentation


その後、調べてみるとlazyasdだけパッケージとして切り離されているようです。

GitHub - xonsh/lazyasd: Lazy & self-destructive tools for speeding up module imports

pipでinstallできるのでやります

pip install lazyasd


lazyasdで一番簡易にモジュール使う時ロードを実現できるのはデコレータを付けることです。

import importlib
from lazyasd import lazyobject

@lazyobject
def os():
    return importlib.import_module('os')

# importが発生するのはココ
print(os.listdir("."))

実際にモジュールを利用する時にimportする事が割りと簡単にできました。

 
compile済みの正規表現も遅延させて読み込めます。

import re
from lazyasd import lazyobject

@lazyobject
def hoge_re():
    return re.compile('hoge')

print(hoge_re.search("hoge piyo str") is not None)

 
より良い方法として、別スレッドで読み込むload_module_in_backgroundもあります。

from lazyasd import load_module_in_background

os = load_module_in_background('os')
print(os.listdir("."))

引数のreplacementsも便利です。

 
関数を引数に取るためlambda使ったりとちょっと面倒ですが、LazyObjectLazyDictといったClassも用意されています。

from lazyasd import LazyObject
from lazyasd import LazyDict
import re

# 最初にosを使う時にglobalにosをimportする
os = LazyObject(lambda: importlib.import_module('os'), globals(), 'os')
print(os.listdir("."))

# 一回目に使う時に正規表現をcompileする
RES = LazyDict({
        'dot': lambda: re.compile('.'),
        'all': lambda: re.compile('.*'),
        'two': lambda: re.compile('..'),
        }, globals(), 'RES')
print(RES["dot"].search("hogehoge.text") is not None)

「使うか分からないので、もし使うなら最初にロードしたいな」という時に使えます。


多分以下を見るかソースコードを見ると良いです。
Lazy & Self-destructive Objects (xonsh.lazyasd) — xonsh 0.6.0.dev151 documentation


 

- xonshrcに書いていく -

ここからはxonshrcに書いておいてxonsh起動までを爆速にしてやろうという話です。

xonshであればpip installする必要もなく、xonsh.lazyasdを利用できます。

使うか分からないがimportを忘れがちなやつを全部こうしてやります

from xonsh.lazyasd import lazyobject

@lazyobject
def os():
    return importlib.import_module('os')

 
全部listに突っ込んでexecしておくSampleです

from xonsh.lazyasd import lazyobject
import importlib

lazy_module_list = ["requests", "numpy", "pandas", "matplotlib",...]

for x in lazy_module_list:
    t = "@lazyobject\ndef {}():\n    return importlib.import_module('{}')".format(x, x)
    exec(t)

これをxonshrcに書いて優勝です


いやいや、絶対コンソール使ってたら使うでしょという物はbackgroundでimportしてやりましょう。

from lazyasd import load_module_in_background

background_module_list = ["os", "sys", "random", "shutil", "linecache",...]
for x in background_module_list:
    exec("{}=load_module_in_background('{}')".format(x, x))


参考:Lazy & Self-destructive Objects (xonsh.lazyasd) — xonsh 0.6.0.dev151 documentation


 

- おわりに -

lazyasd便利なので、環境に合わせてロードするやつとかも拡張として書いていきたい所。

importlibのLoader周りはあんまりExamplesもなくて厳しいですが、まあなんとか。


Xonshアドベントカレンダーの方もよろしくお願いします。

Xonsh Advent Calendar 2017 - Qiita