search
LoginSignup
23

More than 3 years have passed since last update.

posted at

PyOxidizer を試してみた

先日、何気なくはてブを見ていると、マイナビニュースで PyOxidizer なるツールが紹介されているのが目に止まりました。

PyOxidizer とはなにか :question:

記事に書かれていた説明によると、

「PyOxidizer」はPythonスクリプトをそのスクリプトを実行するのに必要になるパッケージやモジュールも含めて単一のバイナリファイルにまとめるツール。

だとか。
ふむふむ、これはきっと PyInstaller 的なやつだな。
:point_up_2: の説明を読む限り、これもきっとバイナリサイズが大きくなりそうだけど、PyInstaller と比べてどうなんだろう? ってことで、何はともあれ試してみましょう。

公式ドキュメント

バイナリ作ってみよう :ten:

PyOxidizer は Rust で作られています。
なので、環境ができていれば導入は簡単。

cargpo install pyoxidizer

rust には詳しくないんですが、凄まじい数の依存パッケージ数です…
しばらくまってると、pyoxidizer のビルドが完了します。
windows だと PATH まで通してくれるようですが、mac は PATH 追加してください。

まずはヘルプを見てみましょう。

$ pyoxidizer --help
PyOxidizer 0.2.0
Gregory Szorc <gregory.szorc@gmail.com>
Build and distribute Python applications

USAGE:
    pyoxidizer [FLAGS] [SUBCOMMAND]

FLAGS:
    -h, --help       
            Prints help information

    -V, --version    
            Prints version information

        --verbose    
            Enable verbose output


SUBCOMMANDS:
    add                             Add PyOxidizer to an existing Rust project. (EXPERIMENTAL)
    analyze                         Analyze a built binary
    build                           Build a PyOxidizer enabled project
    build-artifacts                 Process a PyOxidizer config file and build derived artifacts
    help                            Prints this message or the help of the given subcommand(s)
    init                            Create a new Rust project embedding Python.
    python-distribution-extract     Extract a Python distribution archive to a directory
    python-distribution-licenses    Show licenses for a given Python distribution
    run                             Build and run a PyOxidizer application
    run-build-script                Run functionality that a build script would perform

ほぅ:frowning: なんか難しそうだ。

ドキュメントに従って pyapp というディレクトリを作って、

pyoxidizer init pyapp
cd ./pyapp

すると、pyapp の中にわんさかファイルができています。

インタプリタを起動するバイナリ

ここまでの状態で

pyoxidizer run

すると、python のインタプリタが起動しました。

>>> import sys
>>> print(sys.version)
3.7.3 (default, Jun 17 2019, 22:24:24) 
[Clang 6.0.1 (tags/RELEASE_601/final)]
>>> 

ふむ、細かいことはちゃんと調べねばなりませんが、ローカルでインストールしている python とはバージョンが異なります。

さて、run コマンドのヘルプメッセージにあるように、Build & Run なので、
pyapp/build/apps/pyapp/debug
にもインタプリタを起動するバイナリが作成されていると思います。

最初は「これいつ使うねん」とか思ったけど、python をインストールできないような環境に既存の python スクリプトを持ってって実行できたら幸せなシチュエーションも確かにあるわけで、ひょっとするとすごく便利かもしれないと思い始めてる。ワナワナ

python スクリプトのバイナリ

では、いよいよ本題の、スクリプトのバイナリを作りましょう。
今回は helloworld.py と、requestsのモジュールをimportするmodsample.pyで試してみます。

helloworld.py
import sys


def main():
    print("hello world.")


if __name__ == "__main__":
    sys.exit(main())

modsample.py
import requests

target_url = "https://www.google.com/"
r = requests.get(target_url)

print(r.status_code)

pyapp/pyoxidizer.tomlが設定ファイルのようなので、これの82行目にある[[embedded_python_run]]を修正します。
バイナリ化するファイルは pyapp/helloworld.pyに配置して、

[[embedded_python_run]]
# mode = "repl"
mode = "module"
module = "helloworld"

して、runすると、、、

AttributeError: 'NoneType' object has no attribute 'loader'
error: cargo run failed

あれ?
どうやら:point_down:も必要みたい。

[[packaging_rule]]
type = "package-root"
path = "."
packages = ["helloworld"]

hello world.

いぇーい :tada::tada::tada:
pyapp/build/apps/pyapp/debugにもちゃんとバイナリできてる。

ただ hello worldを出力するだけなら、:point_down:みたく直接スクリプトの記述もできるみたい。

[[embedded_python_run]]
# Evaluate some Python code.
mode = "eval"
code = "print('hello world')"

続いてpyapp/modsample.py
さっきの設定に加えて import するモジュールの設定が必要です。

[[embedded_python_config]]
sys_paths = ["$ORIGIN/lib"]

[[packaging_rule]]
type = "pip-install-simple"
package = "requests"
install_location = "app-relative:lib"
extra_args = ["--proxy=url:port and other args"]

[[packaging_rule]]
type = "package-root"
path = "."
packages = ["modsample"]

[[embedded_python_run]]
mode = "module"
module = "modsample"

これで、

200

やったー :tada::tada::tada:

その後、matplotlib や numpy など試してみたんですがうまくできず、もう少し調べてみる必要がありそうです。

PyInstaller と比べてどうか :thinking:

上記で使用した modsample.py を windows 上で PyInstaller --onefile すると、大体6MBくらいになります。一方で、PyOxidizer で今回の手順で作成したバイナリは20MBほどあり、requests モジュールが別ファイルになってしまっていたので、今回の結果だけで単純には比較できませんでした。

install_location = "embedded"で単一バイナリになるみたいですが、それができないモジュールもあるみたい…

もう少しドキュメントを読みこんで、いろいろ設定をいじってみる必要がありそうです:muscle:

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
What you can do with signing up
23