Python の標準インポートをカスタマイズしてみよう!5.6

当サイトではアフィリエイト広告を利用しています。

リファレンス

Python の import 文は普段何気なく使っていますが、実はカスタマイズできることをご存じですか?
今回は、Python の標準インポートシステムをカスタマイズする方法を、初心者でも分かりやすいようにコード付きで解説します!


カスタムインポートの目標

Python の標準 import 機能を置き換えることで、独自のインポートロジックを作る方法を学びます。

これを応用すれば、例えば次のようなことが可能になります:

  • 特定のモジュールだけ暗号化してインポート
  • ネットワーク越しにリモートモジュールをダウンロード
  • Python コードをデータベースから動的にロード

では、実際に試してみましょう!


ステップ 1: 通常のインポートを確認

まず、Python の標準的な import がどのように動作するかを見てみます。

(1) シンプルなモジュールを作る

mymodule.py というファイルを作成し、次のように書きます。

# mymodule.py
def hello():
    print("Hello from mymodule!")

(2) import して実行

次に、このモジュールをインポートし、関数を呼び出してみましょう。

import mymodule

mymodule.hello()

出力

Hello from mymodule!

ステップ 2: sys.meta_path を使ってカスタムインポート

Python では、sys.meta_path を使うことでインポートの動作を変更できます。

(1) インポートフックを定義

まず、カスタムのインポートローダーを作成してみます。
次のコードを custom_importer.py というファイルに保存してください。

import sys
import importlib.abc
import importlib.util
import os

class CustomImporter(importlib.abc.MetaPathFinder):
    def find_spec(self, fullname, path, target=None):
        print(f"カスタムインポート試行中: {fullname}")

        # `mymodule` を特別扱いする
        if fullname == "mymodule":
            filename = fullname + ".py"
            if filename in os.listdir():
                print(f"{filename} をカスタムローダーでロード")
                spec = importlib.util.spec_from_file_location(fullname, filename)
                return spec
        return None  # 他のモジュールは通常の `import` に任せる

# `sys.meta_path` にカスタムインポーターを追加
sys.meta_path.insert(0, CustomImporter())

この CustomImporter クラスは mymodule を見つけると、その場所を教えるだけのシンプルなカスタムファインダーです。


(2) カスタムインポートの動作を確認

次に、この custom_importer.py をスクリプト内で実行し、mymodule をインポートしてみましょう。

import custom_importer  # これでカスタムインポートが有効になる
import mymodule  # `mymodule` のインポートを試す

mymodule.hello()

出力

カスタムインポート試行中: mymodule
mymodule.py をカスタムローダーでロード
Hello from mymodule!

このように、通常の import を横取りして、カスタムローダーが mymodule.py をロードしていることが分かります。


ステップ 3: カスタムローダーを追加

find_spec だけでは、モジュールの「場所」を見つけることしかできません。
実際にモジュールをロードするには、カスタムローダーが必要です。

そこで、ローダーを作成し、インポート時の挙動を制御してみましょう。

class CustomLoader(importlib.abc.Loader):
    def create_module(self, spec):
        return None  # デフォルトのモジュール作成を使用

    def exec_module(self, module):
        print(f"{module.__name__} をカスタムローダーで実行中")
        with open(module.__name__ + ".py", "r", encoding="utf-8") as f:
            exec(f.read(), module.__dict__)

class CustomImporter(importlib.abc.MetaPathFinder):
    def find_spec(self, fullname, path, target=None):
        print(f"カスタムインポート試行中: {fullname}")
        filename = fullname + ".py"
        if filename in os.listdir():
            print(f"{filename} をカスタムローダーでロード")
            spec = importlib.util.spec_from_file_location(fullname, filename, loader=CustomLoader())
            return spec
        return None  # 他のモジュールは通常の `import` に任せる

# `sys.meta_path` にカスタムインポーターを追加
sys.meta_path.insert(0, CustomImporter())

このコードを使うと、インポート時に exec_module が呼ばれ、mymodule.py のコードが実行されます。


ステップ 4: 実際に動かしてみる

スクリプトを実行すると、次のような出力が得られます。

import custom_importer  # カスタムインポーターを登録
import mymodule  # `mymodule` のインポートを試す

mymodule.hello()

出力

カスタムインポート試行中: mymodule
mymodule.py をカスタムローダーでロード
mymodule をカスタムローダーで実行中
Hello from mymodule!

これで、カスタムローダーが mymodule を適切に処理できることが確認できました!


まとめ

  1. sys.meta_path にカスタムファインダーを登録すると、import の動作を変更できる。
  2. find_spec を実装すれば、特定のモジュールの検索方法をカスタマイズできる。
  3. Loader を実装すると、インポートしたモジュールの実行を制御できる。

この方法を応用すると、例えば:

  • .zip ファイル内の Python モジュールを直接インポート
  • ネットワーク経由でモジュールを取得して実行
  • モジュールのコードを暗号化・復号化して安全にインポート

など、いろいろなカスタムインポートの仕組みを作れます!

Python の柔軟なインポートシステムを活用して、独自の機能を実装してみてください! 🚀