LoginSignup
2
3

More than 3 years have passed since last update.

モジュールの初回importにHookして、モジュールパスをprintする

Last updated at Posted at 2019-12-29

初めてのimportにHookして、モジュールパスをprintする

TL;DR

  • Python2.7.141で、モジュールの初回Import後にフックして実行する仕組みが欲しかった。
    • Python3系ではもっとスマートな方法があるはず。
  • モジュールの初回Import時に読み込み元のファイルパスを表示するスクリプトを作成した。
  • 本内容はAutodesk Mayaとは一切関係がないが、
    MayaやHoudini、Blenderなど、Pythonインタプリタを利用しているツール上であればつきまとう(気がする)問題への、
    一つの答えになりそうなのでタグを付けている。

モチベーション

  • 長く運営が続いたプロジェクトは、内製ツールも増えていく。
    ツールの利用状況を調べることで、不要になったツールをある程度削除することは可能だが、
    ライブラリや設計が悪いと、スクリプトファイルの依存関係の把握すら満足に出来ず、
    消したら何か環境が壊れることを恐れて2、削除できなくなってしまう。
  • 不要なスクリプトファイルが増えることで、無駄なファイルの保守や引き継ぎコストが上がってしまうが、人手は足らなくなる一方。
  • 初回Import時にHookする仕組みを作り、ファイル単位の利用状況を集計することで、
    不要なスクリプトファイルを発見し、安易に削除できるようにして、無駄なコストを減らしたい。
  • ついでにモジュールの読み込み元も把握しておきたい。

コード

firstimport_hook.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import __builtin__
import types
import copy
import sys

import inspect

# reload() 対策のため __import__ がBuiltinFunctionか確認した上で格納する。
if isinstance(__import__, types.BuiltinFunctionType):
    _original__import__ = copy.deepcopy(__import__)

# 他のモジュールでimport hookされている場合などに、
# _original__import__ が定義出来ないので適当にエラーを吐く
elif '_original__import__' not in globals():
    raise ImportError('Definition of _original__import__ failed.')


def __get_modulefile_from_load_module():
    try:
        # 読み込み元のモジュールファイルを取得する
        currentframe = inspect.currentframe()
        module_file = currentframe.f_back.f_back.f_code.co_filename
    finally:
        # 参照カウントを正しくするためにdel。
        del currentframe

    return module_file


def print_modulefile_post_first_import(*args, **kwargs):
    """初回インポート後にモジュールファイルを表示する"""

    # importする前のモジュール読み込み状況を取得
    loaded_module_names = sys.modules.keys()

    # __import__を実行
    module = _original__import__(*args, **kwargs)

    # 既に一度ロードしたことがあったモジュールは処理しない
    if module.__name__ in loaded_module_names:
        return module

    # __file__ がないモジュールのパスは表示しない
    filepath = getattr(module, '__file__', None)
    if filepath:
        path_from_load_module = __get_modulefile_from_load_module()

        print('%s -> %s' % (path_from_load_module, filepath))

    return module


def enable_import_hook():
    """インポートフックを有効にする"""
    __builtin__.__import__ = print_modulefile_post_first_import


def disable_import_hook():
    """インポートフックを無効にする"""
    __builtin__.__import__ = _original__import__


if __name__ == '__main__':
    # importフックを有効にする
    enable_import_hook()

    import json
    reload(json)
startup.py
import firstimport_hook
firstimport_hook.enable_import_hook()

最後に

これで、Pythonファイルの初回Import時に、読み込んだモジュールのパスがprint()できるようになった。
あとは適当にサーバーに送信すれば集計なども可能だろう。

mel?知らない子ですね……


  1. 正確にはMayaのPythonインタプリタ上で。 

  2. 検閲済み。 

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3