Hirologue

年齢を理由にエンジニアになることを諦めないミドルの成長記録

EP 14: 敵の名は (sysモジュールについて)

はじめに

前回の記事では、Dockerを使ってシンプルなPython環境を構築しました。

hirologue.hateblo.jp

Pythonには標準ライブラリと呼ばれる便利機能が標準搭載されています。

どのような便利機能があるのかは、「公式ドキュメント」を読んでみればわかると言う人もいます。

Python 標準ライブラリ — Python 3.11.8 ドキュメント

しかし、この「公式ドキュメントを読む」ことは初学者にとっては、非常にハードルが高く感じて、どうしても避けてしまいがちです。
読むのが面倒なことに加えて、そもそも説明が理解しづらく、「ちょっと何言ってるかわかんない」状態になってしまうからではないかと個人的には思います。

そこで、今回は標準ライブラリの中からsysモジュール(以下、sys)に着目して、できることをいくつか紹介したいと思います。


sysで何ができるか

sysの公式ドキュメントについては、こちらを参照してください。

docs.python.org

やはり難解ですね。
sysを使うことで、Pythonインタプリタで使用する変数の情報を取得したり変更できるようになります。
また、インタプリタの動作に関連する関数も定義されています。

これらについて実際にコードを書いて確認してみましょう。

変数の情報の取得と変更

パスを取得 - sys.path

sys.pathの変数には、Pythonがモジュールをインポートするときに検索するパスがリスト型で格納されています。

このことを確認するために"my_sys.py"ファイルを新規作成して、次のコードを入力します。

import sys


print(f'sys.pathの型: {type(sys.path)}')
for i, r in enumerate(sys.path, 1):
    print(f'sys.pathの{i}番目: {r}')


では、$ docker-compose run --rm app shでコンテナを起動して、my_sys.pyを実行します。

my_sys.pyの実行結果

sys.pathがリスト型であることが確認できました。
また、sys.pathの中身をfor文で回した結果を見ると、5つのパスが格納されていることがわかりました。

Pythonはモジュールをインポートするときに、sys.pathに格納されたパスの順番にしたがってモジュールを検索します。 そして、最初にヒットしたモジュールをインポートします。
このことから、ライブラリやモジュールをインポートできないエラーが発生した場合には、sys.pathを確認して、リストにパスがあるか調べた方が良さそうですね。

また、sys.pathの1番目にはカレントディレクトリが入ります(この場合は、python_basicディレクトリ)。
つまりimport文を実行するときには、まずはカレントディレクトリに対象のモジュールがあるか調べているのです。

インポートするモジュール名とファイル名が同じ場合の挙動

ここで、簡単な実験をしてみます。
新規ファイルとして、"math.py"を作成して、標準ライブラリのmathモジュールをインポートして簡単なprint文を記載します。

import math


print('hello math.py!')


このファイルを実行すると・・・

math.pyの実行結果

print文が2回出力されてしまいます。

なぜでしょうか?

これは、ファイル名が"math.py"であることに起因します。
このコードは、次のように実行されます。

  1. import文が実行されます
  2. sys.pathの順番にしたがってモジュールを検索します
  3. カレントディレクトリにあるmath.pyがヒットするのでインポートします
  4. インポートしたmath.pyのprint文が実行されます
  5. importの処理が終わって次の行にあるprint文が実行されます

だから、print文が2回実行されたのです。

このようなバグが生まれるので、インポートするモジュール名と作成するファイル名は別にするのが一般的です。

sys.pathの書き換え

では、どうしてもインポートするモジュール名とファイル名を同じにしたい場合は、どうすればいいか考えてみます。

sys.pathはリストなので、リストの中身を変更すれば実現できそうです。
sysをインポートして、カレントディレクトリを含まないようにsys.pathを書き換えます。

import sys

sys.path = sys.path[1:]

import math


print('hello math.py!')


実行してみると・・・

sys.path変更後の実行結果

できました。

今回は、sys.pathを変更できるという実験のためにこのようなことをしてみました。
普段はインポートするモジュール名とファイル名は同じにしないことが推奨されますので、その点はご注意ください。


sys.argvとsys.exit()

コマンドライン引数 - sys.argv

Pythonファイルは、$ python <filename.py>で実行できますが、この時にコマンドライン引数を渡すこともできます。

ここで新しくファイルを作成します。
ファイル名は"command_line_args.py"とします。

import sys


print(type(sys.argv))
for i, arg in enumerate(sys.argv):
    print(f'arg[{i}]: {arg}')


では、このファイルを次のようにコマンドライン引数をつけて実行してみます。

$ python command_line_args.py option1 option2

command_line_args.pyの実行結果

このようにsys.argvはリスト型で、インデックス番号0には実行したファイル名が入っています。
そして、インデックス番号1以降にコマンドラインから渡された引数が入ります。
このようにsys.argvには、コマンドライン引数が渡され、呼び出すことが可能となります。

プログラムの終了 - sys.exit()

sys.exit()によって、実行中のプログラムを終了することができます。
また、sys.exit()にはエラーメッセージなどの文字列を渡すこともできます。

先ほど作成したcommand_line_args.pyに以下のコードを追加します。

import sys

print(type(sys.argv))
for i, arg in enumerate(sys.argv):
    print(f'arg[{i}]: {arg}')

# 以下のコードを追加します
sys.exit('sys.exitを実行します')

print('このprint文は出力されません')  # これは実行されません


そして、先ほどと同じようにコマンドライン引数をつけて実行します。

$ python command_line_args.py option1 option2

sys.exit()の実行結果

このようにsys.exit()以降に記述したコードは実行されずにプログラムが終了します。

sys.argvとsys.exit()の組み合わせ

sys.exit()とsys.argvを組み合わせることでプログラム実行時の挙動を動的に変化させることが可能になります。

例えば、コマンドライン引数で入力を許可するリストを用意して、リストにないコマンドライン引数が入力された場合は、エラーメッセージを表示させるといったことができるようになるのです。


いかがだったでしょうか。
sysが使えるようになるとプログラムを柔軟に制御できるということができます。
確かに公式ドキュメントは初学者にとっては取っつきにくいものですが、実際に手を動かしてみると理解が少しずつ深まるのではないでしょうか。
今のご時世、公式ドキュメントを読解するのが難しすぎる場合は、AIに要約してもらって、何ができるのかを把握するのも手です。
せっかく用意された便利機能です。
使わないのはもったいないですし、まずはガンガン手を動かして理解していきましょう!

この記事が参考になれば幸いです。
では✋