ネットで見つけたPythonのコードをのぞくと、先頭に import sys と書かれていることがよくあります。でも、このsysが何をするモジュールなのか、説明できる人は意外と少ないのではないでしょうか。
名前からして難しそうですし、初心者のうちは見なかったことにしがちですよね。私も学び始めたころは、おまじないのように書き写していました。
sysは、いま動いているPythonそのものとやり取りするための標準ライブラリです。プログラムに渡された引数を受け取ったり、終了のしかたを決めたり、Pythonのバージョンを調べたりできます。
今回は、sysの中でも特によく使う機能に絞って、手元のPython 3.11で動かした結果とともに紹介します。
sysモジュールとは何か¶
まずは、sysがどんな立ち位置のモジュールなのかを押さえておきましょう。sysはsystemの略で、Pythonの実行環境に関する情報や操作をまとめたモジュールです。
ここでいうシステムは、パソコン全体ではありません。いま動いているPythonインタープリタ自身のことを指しています。インタープリタとは、書いたコードを1行ずつ読み取って実行してくれるプログラムのことです。
標準ライブラリなので、pipでのインストールは不要です。Pythonが入っていれば、import sys と書くだけで使えます。
よく似た名前にosモジュールがあります。ざっくり言うと、osはパソコンのOSとやり取りする道具で、sysはPython自身とやり取りする道具です。ファイルやフォルダを操作したいならos、Pythonの設定や状態を知りたいならsys、と覚えておくと迷いにくくなります。
よく使う機能を一覧で見てみよう¶
sysにはたくさんの機能がありますが、初心者が実際に使うものは限られています。最初に、この記事で扱うものを表で整理しておきます。
| 名前 | 種類 | できること |
|---|---|---|
| sys.argv | リスト | コマンドライン引数を受け取る |
| sys.exit() | 関数 | 終了コードを指定してプログラムを終える |
| sys.path | リスト | モジュールを探す場所を確認する |
| sys.version_info | タプル | Pythonのバージョンを調べる |
| sys.platform | 文字列 | 動いているOSの種類を調べる |
| sys.stdin / stdout / stderr | ファイル | 入力・出力・エラー出力を扱う |
| sys.getsizeof() | 関数 | オブジェクトのメモリ上のサイズを調べる |
| sys.getrecursionlimit() | 関数 | 再帰呼び出しの上限を確認する |
sys.argvでコマンドライン引数を受け取る¶
sysの中でいちばん出番が多いのが、sys.argvです。argvはargument vectorの略で、プログラムを起動したときに渡された引数が入っています。
たとえば、ターミナルで python greet.py hello 3 のように実行したとしましょう。このとき、スクリプト名のうしろに書いたhelloと3がコマンドライン引数です。
中身を確かめるために、次の2行だけのファイルを用意しました。
import sys
print(sys.argv)
これをa.pyという名前で保存し、python a.py hello 3 と実行すると次のように表示されました。
['a.py', 'hello', '3']
先頭の要素には、実行したスクリプトの名前が入ります。そのうしろに、渡した引数が順番に並んでいる形です。
引数はすべて文字列になる¶
ここで注意したいのが、3という数字も文字列の '3' として入っている点です。sys.argvの要素は、どれも文字列として渡されます。
数として計算に使いたいときは、int(sys.argv[2]) のように自分で変換する必要があります。これを忘れて足し算をすると、3+3が33になるような不思議な結果になるので気をつけてください。
引数を渡し忘れたときに、sys.argv[1] を参照してIndexErrorになるのも定番のつまずきです。先に len(sys.argv) で個数を確認しておくと安心です。
引数の数が増えてきたら、標準ライブラリのargparseに切り替えるのがおすすめです。ヘルプの表示や型の変換まで面倒を見てくれます。【関連記事】Pythonでコマンドラインツールを作るには?argparse入門
sys.exitでプログラムを終わらせる¶
次は、プログラムを途中で終わらせるsys.exitです。単に止めるだけなら、ほかにもexitやquitがありますよね。
exitやquitは、対話モードで使うために用意された関数です。スクリプトの中で終了させたいときは、sys.exitを使うのが正しいお作法とされています。この違いは、こちらの記事でくわしく解説しています。【関連記事】Pythonの抜け方は?exit関数の使い方を解説
sys.exitの引数には、終了コードを渡せます。終了コードとは、プログラムが成功したのか失敗したのかを、呼び出し元に伝えるための数字です。
渡す値によって、動きが次のように変わります。
| 引数 | 終了コード | 動き |
|---|---|---|
| 省略、またはNone | 0 | 成功として終わる |
| 整数 | その数 | 0なら成功、0以外なら失敗 |
| 文字列など整数以外 | 1 | その内容をエラー出力に表示して失敗として終わる |
文字列を渡したときの動きを確かめてみました。メッセージが表示されたあと、終了コードを表す $? は1になっています。
エラー: ファイルがありません
code=1
慣習として、Unix系のプログラムでは、コマンドラインの使い方の誤りに2、それ以外のエラーに1を使うことが多いと公式ドキュメントでも紹介されています。最初は、成功なら0、失敗なら0以外と覚えておけば大丈夫です。
sys.exitの正体は例外¶
意外に思うかもしれませんが、sys.exitは内部でSystemExitという例外を投げているだけです。そのため、try文のfinallyに書いた後片づけの処理は、きちんと実行されます。
裏を返すと、例外をまとめて受け止めるexceptを書いていると、終了そのものを握りつぶしてしまうことがあります。実際に、SystemExitをexceptで受け止めてみると、次のように捕まえられました。
import sys
try:
sys.exit(3)
except SystemExit as e:
print("caught", e.code)
実行すると caught 3 と表示され、プログラムは終わらずに先へ進みます。終了したつもりなのに処理が続いて困ったときは、どこかで例外を広く受け止めていないか疑ってみてください。
sys.pathでモジュールの探し場所を知る¶
import を書いたとき、Pythonはどこからそのモジュールを探しているのでしょうか。その答えが入っているのが、sys.pathです。
sys.pathは、モジュールを探すフォルダのパスを並べたリストです。Pythonは先頭から順番にフォルダをのぞき、最初に見つかったモジュールを読み込みます。
先頭の要素には、基本的に実行したスクリプトが置かれているフォルダが入ります。先ほどのa.pyで print(sys.path[0]) を実行すると、ファイルのあるフォルダが表示されました。
/root/work
ModuleNotFoundErrorに悩んだときは、sys.pathを表示してみるのが近道です。インストールしたはずの場所がリストに入っていなければ、別のPythonで実行している可能性が高いと見当がつきます。【関連記事】PythonのModuleNotFoundErrorって何?解決するにはどうしたらいいの?
自分のファイル名に注意¶
sys.pathの先頭がスクリプトのフォルダだということは、同じフォルダのファイルが優先されるということです。ここに、初心者がはまりやすい落とし穴があります。
たとえば、練習用のファイルをrandom.pyという名前で保存したとします。すると、標準ライブラリのrandomではなく、自分のrandom.pyが読み込まれてしまうことがあります。
sys.version_infoとsys.platformで実行環境を調べる¶
ここからは、実行環境を調べるための機能を見ていきます。チームで同じコードを動かすとき、相手のPythonが古くて動かないという経験はありませんか。
sys.version_infoには、Pythonのバージョンが名前付きタプルとして入っています。手元の環境では次のように表示されました。
sys.version_info(major=3, minor=11, micro=17, releaselevel='final', serial=0)
便利なのは、タプルどうしで大小を比べられることです。sys.version_info >= (3, 10) と書けば、3.10以上かどうかをTrueかFalseで判定できます。
sys.platformには、動いているOSの種類が入っています。Linuxならlinux、Windowsならwin32、macOSならdarwinという文字列です。OSによって処理を分けたいときの目印になります。
標準入出力とエラー出力を使い分ける¶
続いて、sys.stdin、sys.stdout、sys.stderrの3つです。それぞれ標準入力、標準出力、標準エラー出力と呼ばれる、データの通り道を表しています。
ふだん使っているprint関数は、標準出力に文字を書き出しています。input関数は、標準入力から1行を読み取っています。
では、標準エラー出力は何のためにあるのでしょうか。答えは、結果とエラーメッセージを分けて扱えるようにするためです。
python script.py > out.txt のように実行すると、標準出力の内容はファイルに保存されます。このとき、エラーメッセージまでファイルに混ざってしまうと困りますよね。
print("メッセージ", file=sys.stderr) と書けば、エラー出力に送れます。結果はファイルへ、エラーは画面へと分かれるので、あとから結果を扱う人にもやさしい作りになります。
sys.getsizeofと再帰の上限¶
ここでは、困ったときに役立つ機能を2つ紹介します。
sys.getsizeofは、オブジェクトがメモリ上で何バイト使っているかを返す関数です。手元で空のリストを調べると56、要素が3つのリストでは88と表示されました。
ただし、この数字には落とし穴があります。1000個の要素を持つリストを1つだけ入れたリストを調べると、64しかありませんでした。
これは、getsizeofがそのオブジェクト自身の大きさだけを数えるからです。中から参照している別のオブジェクトの大きさは含まれません。データ全体のメモリ使用量を知りたいときは、この点に注意してください。
もうひとつは、sys.getrecursionlimitです。関数が自分自身を呼び出す再帰には深さの上限があり、手元の環境では1000でした。
上限を超えるとRecursionErrorになります。sys.setrecursionlimitで上限を上げることもできますが、むやみに上げるとPython自体が落ちる危険があります。まずは再帰を使わない書き方を考えるのが先です。【関連記事】Pythonの再帰関数とは?自分を呼び出す関数の考え方とRecursionErrorの直し方を初心者向けに解説
sysを使った小さなスクリプトを作ってみる¶
ここまでの内容を組み合わせて、ファイルの行数を数える小さなスクリプトを作ってみましょう。次のコードを、count_lines.pyという名前で保存します。
import sys
if sys.version_info < (3, 8):
sys.exit("Python 3.8以上で実行してください")
def main():
if len(sys.argv) < 2:
print("使い方: python count_lines.py ファイル名", file=sys.stderr)
return 2
path = sys.argv[1]
try:
with open(path, encoding="utf-8") as f:
count = sum(1 for _ in f)
except FileNotFoundError:
print(f"ファイルが見つかりません: {path}", file=sys.stderr)
return 1
print(f"{path}: {count}行")
return 0
if __name__ == "__main__":
sys.exit(main())
最初にバージョンを確認し、古ければメッセージを出して終わります。main関数では、引数の有無を確かめてから、ファイルを開いて行数を数えています。
ポイントは、main関数が終了コードを返し、それをsys.exitに渡している点です。この形にしておくと、成功と失敗を呼び出し元へきちんと伝えられます。
if __name__ == "__main__": の意味が気になった方は、こちらを読んでみてください。【関連記事】Pythonの「name == 'main'」って何?初心者が必ず疑問に思うこと
3行あるmemo.txtを用意して、3通りの呼び方を試した結果が次のとおりです。
使い方: python count_lines.py ファイル名
code=2
memo.txt: 3行
code=0
ファイルが見つかりません: nai.txt
code=1
引数なしでは2、成功すれば0、ファイルがなければ1が返っています。エラーメッセージはstderrに出しているので、出力をファイルに保存してもメッセージは混ざりません。
現場で感じた、終了コードの大切さ¶
私は10年ほどエンジニアとして開発に関わってきましたが、終了コードを軽く見たせいで痛い目にあったことがあります。夜間に動かしていたデータ取り込みのスクリプトが、エラーを表示しながらも終了コード0で終わっていたのです。
例外をexceptで受け止めてprintするだけにしていたため、外から見ると毎回成功していました。結局、数日分のデータが抜けていることに気づいたのは、利用者からの問い合わせがきっかけでした。
自動実行の仕組みや監視ツールの多くは、メッセージの中身ではなく終了コードで成否を判断します。人が画面を見ていない場面ほど、失敗したら0以外を返すことが大切です。
それ以来、自分の書くスクリプトは必ず、最後にsys.exitで結果を返す形にしています。たった1行の違いですが、トラブルに早く気づけるかどうかを大きく左右します。
まとめ¶
sysは、いま動いているPython自身とやり取りするための標準ライブラリです。たくさんの機能がありますが、最初はよく使うものだけ覚えておけば十分です。
コマンドライン引数はsys.argvで受け取り、中身はすべて文字列だと意識すること。終了するときはsys.exitで、成功なら0、失敗なら0以外を返すこと。この2つを押さえるだけで、スクリプトの完成度がぐっと上がります。
モジュールが見つからないときはsys.path、バージョンを確かめたいときはsys.version_infoと、困ったときの調べ道具としても頼りになります。次に import sys を見かけたら、どの機能を使っているのか探してみてください。
ここまでお読みいただきありがとうございました。