複数pythonファイルを一つのpythonファイルにする


やりたいこと

例えば, main.pyが他のファイルを呼び出すようなプロジェクトを...

- main.py
- sub1.py
- folder
    - sub2.py

↓↓↓ 依存してる関数やクラスなどをちゃーんと...

onefile.py

ぎゅ!!!っとする.

(動機としては, プロジェクト管理しているpythonファイルを楽にgoogle colabで実行できないかな〜と思ったからです.)

解決法

こちら, stickytapeというpythonパッケージを使えばOK.
https://pypi.org/project/stickytape/

stickytape = 粘着テープ

実験

実験したコードは全てGithubに公開してますので, 参考までに.

install

これでstickytapeコマンドが使えるようになる. (コマンド名なげぇ...)

$ pip install stickytape

ファイルを用意

以下のような構成とします.

- main.py
- sub1.py
- folder
    - sub2.py

folder/sub2.py

適当にAppleクラスとか作って, 適当にvalueプロパティとか持たせときます.

class Apple:

    def __init__(self, value):
        self.value = value

sub1.py

適当に平均関数とか作っときます.

sub1.py
def mean(a, b):
    return (a+b)/2

main.py

importしてきて適当に計算させて適当に表示します.

from sub1 import mean
from folder.sub2 import Apple

apple1 = Apple(value=100)
apple2 = Apple(value=200)

result = mean(apple1.value, apple2.value)
print(result)

いざ!一つのファイルに!

以下のコマンドを実行します. (もちろんonefile.pyのところは何でもOK)

$ stickytape main.py > onefile.py

結果

以下のようなonefile.pyが生成されます.

#!/usr/bin/env python


import contextlib as __stickytape_contextlib

@__stickytape_contextlib.contextmanager
def __stickytape_temporary_dir():
    import tempfile
    import shutil
    dir_path = tempfile.mkdtemp()
    try:
        yield dir_path
    finally:
        shutil.rmtree(dir_path)

with __stickytape_temporary_dir() as __stickytape_working_dir:
    def __stickytape_write_module(path, contents):
        import os, os.path

        def make_package(path):
            parts = path.split("/")
            partial_path = __stickytape_working_dir
            for part in parts:
                partial_path = os.path.join(partial_path, part)
                if not os.path.exists(partial_path):
                    os.mkdir(partial_path)
                    open(os.path.join(partial_path, "__init__.py"), "w").write("\n")

        make_package(os.path.dirname(path))

        full_path = os.path.join(__stickytape_working_dir, path)
        with open(full_path, "w") as module_file:
            module_file.write(contents)

    import sys as __stickytape_sys
    __stickytape_sys.path.insert(0, __stickytape_working_dir)

    __stickytape_write_module('sub1.py', 'def mean(a, b):\n    return (a+b)/2')
    __stickytape_write_module('folder/sub2.py', 'class Apple:\n\n    def __init__(self, value):\n        self.value = value')
    from sub1 import mean
    from folder.sub2 import Apple

    apple1 = Apple(value=100)
    apple2 = Apple(value=200)

    result = mean(apple1.value, apple2.value)
    print(result)

一瞬「何じゃこりゃ!?」となりましたが, これを実行すると...

150.0

無事正しい計算結果が表示されました.

Google Colabで実験

先ほどのコードをGoogle Colabにコピペして実行しました.

下のように, 無事150.0が表示されました.

(虹色の猫が歩いてるのは気にしないでください.)

スクリプト化

ここからは余談です.

stickytapeというコマンドは長いですし, いちいち生成ファイルのディレクトリを指定するのとか怠いので, 次のようにスクリプト化しておくといいかと思います.

- main.py
- sub1.py
- folder
    - sub2.py
- scripts
    - tape.sh
- build
    - onefile.py
tape.sh
# 初期値
entry="main.py"
output="onefile.py"

# オプション
while getopts e:o: OPT
do
    case $OPT in 
        "e" ) entry=${OPTARG};;
        "o" ) output=${OPTARG};;
    esac
done

# 実行
stickytape ${entry} > "build/${output}"

以下のコマンドで, main.pyを実行し, buildディレクトリにonefile.pyを生成してくれます.

$ sh scripts/tape.sh

オプションも用意しておきました.

オプション名 説明
-e エントリポイントのファイル名
-o アウトプットするファイル名
$ sh scripts/tape.sh -e <ファイル名> -o <ファイル名>

生成されるディレクトリはbuildで固定にしているので, 嫌なら勝手に変えてください.

自己紹介

冒頭に書くと邪魔になるので最後にひっそりと自己紹介させてください。

名前 綿岡晃輝
学校 神戸大学大学院
学部の研究 機械学習, 音声処理
大学院の研究 機械学習, 公平性, 生成モデル, etc
Twitter @Wataoka_Koki

Twitterフォローしてね!