29. オペレーティングシステムとの連携: sys と os
Python プログラムは単独で存在しているわけではありません。ファイル、ディレクトリ、環境設定、そしてプログラムの起動や通信の方法を管理するオペレーティングシステム上で動作します。sys と os モジュールは、この環境とやり取りするためのツールを提供し、コマンドライン引数の受け取り、環境変数の読み取り、ファイルシステムの移動、ディレクトリの作成や削除をプログラムから行えるようにします。
これらのモジュールを理解すると、Python スクリプトは孤立したコードから、より広いシステムと統合でき、起動時のユーザー入力に応答し、ファイルやフォルダーをプログラムで管理できるプログラムへと変わります。
29.1) コマンドライン引数と sys.argv の利用
ターミナルやコマンドプロンプトから Python スクリプトを実行するとき、追加情報を渡せます。これらは コマンドライン引数(command-line arguments) と呼ばれます。たとえば次のようになります。
python greet.py Alice 25ここでは、greet.py がスクリプト名で、Alice と 25 がプログラムに渡される引数です。sys モジュールは sys.argv を通じてこれらの引数にアクセスできます。これは、スクリプト名とすべての引数を文字列として含むリスト(list)です。
29.1.1) sys.argv とは?
sys.argv は次のようなリスト(list)です。
sys.argv[0]は常に実行されているスクリプト名ですsys.argv[1]、sys.argv[2]などは、スクリプト名の後に渡された引数です- 見た目が数字でも、すべての要素は文字列です
これがどのように動くかを見るために、簡単なスクリプトを作ってみましょう。
# show_args.py
import sys
print("Script name:", sys.argv[0])
print("Number of arguments:", len(sys.argv))
print("All arguments:", sys.argv)このスクリプトを次のように実行すると:
python show_args.py hello world 123Output:
Script name: show_args.py
Number of arguments: 4
All arguments: ['show_args.py', 'hello', 'world', '123']sys.argv に 4 つの要素が含まれていることが分かります。スクリプト名に加えて 3 つの引数です。数値の 123 は整数ではなく、文字列の '123' として格納されています。
29.1.2) プログラムでコマンドライン引数を使う
コマンドライン引数を使うと、コードを変更せずにユーザーがプログラムの挙動をカスタマイズできます。次は、最初の引数を名前として使う挨拶プログラムです。
# greet.py
import sys
if len(sys.argv) < 2:
print("Usage: python greet.py <name>")
sys.exit(1) # エラーコードで終了
name = sys.argv[1]
print(f"Hello, {name}!")これを実行すると:
python greet.py AliceOutput:
Hello, Alice!名前を指定し忘れた場合は次のようになります。
python greet.pyOutput:
Usage: python greet.py <name>このプログラムは、十分な引数が提供されたかどうかをチェックします。足りない場合は使い方のメッセージを表示し、sys.exit(1) で終了します。1 は 終了コード(exit code) です。慣習として、0 は成功を意味し、0 以外の値はエラーを示します。これにより、別のプログラムやスクリプトが、あなたのプログラムが正しく実行されたかどうかを検出できます。
29.1.3) 引数を他の型に変換する
引数はすべて文字列として渡されるため、変換が必要になることがよくあります。次は長方形の面積を計算するプログラムです。
# area.py
import sys
if len(sys.argv) < 3:
print("Usage: python area.py <width> <height>")
sys.exit(1)
try:
width = float(sys.argv[1])
height = float(sys.argv[2])
except ValueError:
print("Error: Width and height must be numbers")
sys.exit(1)
area = width * height
print(f"Area: {area}")これを実行すると:
python area.py 5.5 3.2Output:
Area: 17.6ユーザーが不正な入力をした場合:
python area.py five threeOutput:
Error: Width and height must be numberstry-except ブロック(第 25 章)は変換エラーを適切に処理し、トレースバックでクラッシュする代わりに役に立つフィードバックを提供します。
29.2) sys でインタプリタと実行時情報を取得する
sys モジュールは、Python インタプリタ自体と実行時環境に関する情報を提供します。これは、デバッグ(debugging)、ログ記録、または異なる Python バージョンやプラットフォームに適応するコードを書くときに便利です。
29.2.1) Python バージョン情報
sys.version と sys.version_info は、どの Python バージョンでコードが実行されているかを教えてくれます。
import sys
print("Python version string:", sys.version)
print("Version info:", sys.version_info)
print(f"Major version: {sys.version_info.major}")
print(f"Minor version: {sys.version_info.minor}")Output (example):
Python version string: 3.11.4 (main, Jul 5 2023, 13:45:01) [GCC 11.2.0]
Version info: sys.version_info(major=3, minor=11, micro=4, releaselevel='final', serial=0)
Major version: 3
Minor version: 11sys.version は人間が読める文字列である一方、sys.version_info はプログラムから比較できる名前付きタプル(named tuple)です。
import sys
if sys.version_info < (3, 8):
print("This program requires Python 3.8 or higher")
sys.exit(1)
print("Python version is compatible")これにより、プログラムは対応している Python バージョンでのみ実行されるようになります。
29.2.2) プラットフォーム情報
sys.platform はオペレーティングシステムを識別します。
import sys
print("Platform:", sys.platform)
if sys.platform == "win32":
print("Running on Windows")
elif sys.platform == "darwin":
print("Running on macOS")
elif sys.platform.startswith("linux"):
print("Running on Linux")
else:
print("Running on another platform")Output (on Linux):
Platform: linux
Running on Linuxこれにより、たとえば異なるファイルパスやシステムコマンドを使うなど、必要に応じてプラットフォーム固有のコードを書けます。
29.2.3) モジュール検索パス
sys.path は、モジュールを import するときに Python が検索するディレクトリのリスト(list)です。
import sys
print("Module search paths:")
for path in sys.path:
print(f" {path}")Output (example):
Module search paths:
/home/user/projects
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/home/user/.local/lib/python3.11/site-packages最初の要素は通常、スクリプトが置かれているディレクトリです。import を使うと、Python はこれらのパスを順に検索します。sys.path を理解すると、import エラーのデバッグや、カスタムのモジュールディレクトリを追加するときに役立ちます。
29.2.4) 終了コードでプログラムを終了する
sys.exit() を使ってプログラムを停止する方法を見てきました。成功か失敗かを示すために、終了コードを渡せます。
import sys
def process_data(filename):
try:
with open(filename) as f:
data = f.read()
# データを処理...
return True
except FileNotFoundError:
print(f"Error: File '{filename}' not found", file=sys.stderr)
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python script.py <filename>", file=sys.stderr)
sys.exit(1)
success = process_data(sys.argv[1])
sys.exit(0 if success else 1)終了コードは Unix の慣習に従います。
0は成功を意味します- 0 以外の値は異なる種類のエラーを示します
- 他のプログラムはこれらのコードを確認して、あなたのプログラムが成功したかどうかを判断できます
29.3) os で環境変数にアクセスする
環境変数(environment variables) は、オペレーティングシステムまたはユーザーによって設定され、プログラムが読み取れるキーと値のペアです。設定、パス、API キー、その他ハードコードすべきではない設定の保存に使われます。
29.3.1) 環境変数を読み取る
os モジュールは os.environ を提供します。これは、すべての環境変数を含む辞書のようなオブジェクトです。
import os
# 特定の環境変数を取得
home = os.environ.get('HOME') # Unix/Linux/macOS で
print(f"Home directory: {home}")
# 変数が存在しない場合は None を返す
api_key = os.environ.get('MY_API_KEY')
print(f"API key: {api_key}")Output (on Linux):
Home directory: /home/alice
API key: Noneデフォルト値を指定することもできます。
import os
# デフォルト値付きで環境変数を取得
log_level = os.environ.get('LOG_LEVEL', 'INFO')
print(f"Log level: {log_level}")Output (if LOG_LEVEL not set):
Log level: INFO29.3.2) よくある環境変数
オペレーティングシステムごとに標準的な環境変数があります。
import os
# クロスプラットフォームの変数
print("Path:", os.environ.get('PATH'))
# Unix/Linux/macOS
print("User:", os.environ.get('USER'))
print("Home:", os.environ.get('HOME'))
print("Shell:", os.environ.get('SHELL'))
# Windows
print("User", os.environ.get('USERNAME'))
print("User Profile:", os.environ.get('USERPROFILE'))
print("Temp:", os.environ.get('TEMP'))PATH は特に重要です。実行可能プログラムを探すディレクトリの一覧が入っています。python のようなコマンドを入力すると、システムはこれらのディレクトリを検索します。
29.3.3) 環境変数を設定する
プログラムと、それが作成するサブプロセスに対して、環境変数を変更できます。
import os
# 環境変数を設定
os.environ['MY_CONFIG'] = 'production'
# 読み戻す
print(os.environ.get('MY_CONFIG')) # Output: production
# 環境変数を削除
del os.environ['MY_CONFIG']重要: os.environ への変更は、現在の Python プロセスと、それが起動するプログラムにのみ影響します。プログラム終了後に永続化されたり、他のプログラムに影響したりはしません。
29.4) ファイルパスとディレクトリの操作 (os.path, os.getcwd)
ファイルを扱うプログラムでは、ファイルパスを正しく扱うことが非常に重要です。os と os.path モジュールは、プラットフォームに依存しない方法でパスを組み立て、操作し、問い合わせるためのツールを提供します。
29.4.1) 現在の作業ディレクトリを取得する
現在の作業ディレクトリ(current working directory) (CWD) は、相対パスを解決する際に、プログラムが起点として扱うフォルダーです。
import os
cwd = os.getcwd()
print(f"Current working directory: {cwd}")Output (example):
Current working directory: /home/alice/projects/myapp'data.txt' のような相対パスでファイルを開くと、Python は現在の作業ディレクトリ内を探します。CWD を理解すると、「ファイルが見つからない」エラーのデバッグに役立ちます。
29.4.2) 現在のディレクトリを変更する
os.chdir() で作業ディレクトリを変更できます。
import os
original = os.getcwd()
print("Original directory:", original)
# 別のディレクトリに移動
os.chdir('/tmp')
print("New directory:", os.getcwd())
# 元に戻す
os.chdir(original)
print("Back to:", os.getcwd())Output:
Original directory: /home/alice/projects
New directory: /tmp
Back to: /home/alice/projectsNote: 第 28 章では、元のディレクトリを自動的に復元する contextlib.chdir() を学びました。単純なディレクトリ変更では、コンテキストマネージャ(context manager)を使うことを優先してください。
from contextlib import chdir
with chdir('/tmp'):
print("Temporarily in:", os.getcwd())
# Automatically restoredこれにより、エラーが発生した場合でも必ずディレクトリが復元されます。
29.4.3) os.path.join() でパスを組み立てる
オペレーティングシステムによってパス区切り文字が異なります。
- Unix/Linux/macOS:
/(スラッシュ) - Windows:
\(バックスラッシュ)
os.path.join() は、現在のプラットフォームに合った形で正しくパスを組み立てます。
import os
# サブディレクトリ内のファイルへのパスを作る
data_dir = 'data'
filename = 'users.txt'
filepath = os.path.join(data_dir, filename)
print(f"File path: {filepath}")Output (on Unix/Linux/macOS):
File path: data/users.txtOutput (on Windows):
File path: data\users.txt複数の要素を結合することもできます。
import os
base = '/home/alice'
project = 'myapp'
subdir = 'data'
file = 'config.json'
full_path = os.path.join(base, project, subdir, file)
print(full_path) # Output: /home/alice/myapp/data/config.jsonos.path.join() を使うと、コードをオペレーティングシステム間で移植可能にできます。
29.4.4) パスが存在するかを確認する
ファイルやディレクトリを扱う前に、それらが存在するか確認しましょう。
import os
path = 'data.txt'
if os.path.exists(path):
print(f"'{path}' exists")
else:
print(f"'{path}' does not exist")ファイルかディレクトリかを個別に確認することもできます。
import os
path = 'mydir'
if os.path.isfile(path):
print(f"'{path}' is a file")
elif os.path.isdir(path):
print(f"'{path}' is a directory")
elif os.path.exists(path):
print(f"'{path}' exists but is neither a file nor directory")
else:
print(f"'{path}' does not exist")これらのチェックにより、存在しないファイルを開こうとしたり、存在しないディレクトリを一覧表示しようとしたりする際のエラーを防げます。
29.4.5) 絶対パスを取得する
os.path.abspath() は、相対パスを絶対パスに変換します。
import os
relative_path = 'data/users.txt'
absolute_path = os.path.abspath(relative_path)
print(f"Relative: {relative_path}")
print(f"Absolute: {absolute_path}")Output (example):
Relative: data/users.txt
Absolute: /home/alice/projects/myapp/data/users.txtこれはログ、エラーメッセージ、またはファイルの正確な場所を知る必要がある場合に便利です。
29.4.6) パスを要素に分割する
os.path.split() は、パスをディレクトリとファイル名に分割します。
import os
path = '/home/alice/projects/data.txt'
directory, filename = os.path.split(path)
print(f"Directory: {directory}")
print(f"Filename: {filename}")Output:
Directory: /home/alice/projects
Filename: data.txtos.path.basename() はファイル名だけを取得し、os.path.dirname() はディレクトリだけを取得します。
import os
path = '/home/alice/projects/data.txt'
print(f"Basename: {os.path.basename(path)}") # Output: data.txt
print(f"Dirname: {os.path.dirname(path)}") # Output: /home/alice/projects29.4.7) 拡張子を分割する
os.path.splitext() は、ファイル名と拡張子を分割します。
import os
filename = 'report.pdf'
name, extension = os.path.splitext(filename)
print(f"Name: {name}") # Output: report
print(f"Extension: {extension}") # Output: .pdfこれは、ファイルの種類に基づいて処理する場合に便利です。
import os
files = ['data.csv', 'image.png', 'document.txt', 'script.py']
for file in files:
name, ext = os.path.splitext(file)
if ext == '.csv':
print(f"Process CSV file: {file}")
elif ext == '.png':
print(f"Process image file: {file}")Output:
Process CSV file: data.csv
Process image file: image.png29.5) ファイルとディレクトリの一覧表示、作成、削除
os モジュールは、ファイルシステムを操作するための関数(function)を提供します。ディレクトリ内容の一覧表示、新しいディレクトリの作成、ファイルやフォルダーの削除などです。
29.5.1) ディレクトリ内容を一覧表示する
os.listdir() は、ディレクトリ内のすべての項目のリスト(list)を返します。
import os
# 現在のディレクトリの内容を一覧表示
contents = os.listdir('.')
print("Current directory contents:")
for item in contents:
print(f" {item}")Output (example):
Current directory contents:
script.py
data.txt
mydir
README.mdこのリスト(list)にはファイルとディレクトリの両方が含まれます。それらを区別するには次のようにします。
import os
contents = os.listdir('.')
print("Files:")
for item in contents:
if os.path.isfile(item):
print(f" {item}")
print("\nDirectories:")
for item in contents:
if os.path.isdir(item):
print(f" {item}")Output:
Files:
script.py
data.txt
README.md
Directories:
mydir29.5.2) ディレクトリを作成する
os.mkdir() は単一のディレクトリを作成します。
import os
new_dir = 'output'
if not os.path.exists(new_dir):
os.mkdir(new_dir)
print(f"Created directory: {new_dir}")
else:
print(f"Directory already exists: {new_dir}")Output:
Created directory: outputImportant: 親ディレクトリが存在しない場合、os.mkdir() は失敗します。たとえば 'data' が存在しないときに 'data/output' を作ろうとすると、エラーになります。
os.makedirs() は必要な親ディレクトリをすべて作成します。
import os
nested_dir = 'data/processed/2024'
if not os.path.exists(nested_dir):
os.makedirs(nested_dir)
print(f"Created directory structure: {nested_dir}")Output:
Created directory structure: data/processed/2024これは、存在しなければ data、次に data/processed、次に data/processed/2024 を作成します。
29.5.3) ファイルを削除する
os.remove() はファイルを削除します。
import os
filename = 'temp.txt'
# 一時ファイルを作成
with open(filename, 'w') as f:
f.write('Temporary data')
print(f"File exists: {os.path.exists(filename)}") # Output: True
# ファイルを削除
os.remove(filename)
print(f"File exists: {os.path.exists(filename)}") # Output: FalseWarning: os.remove() はファイルを完全に削除します。ごみ箱には移動されません。
29.5.4) ディレクトリを削除する
os.rmdir() は空のディレクトリを削除します。
import os
directory = 'empty_dir'
# 空のディレクトリを作成してから削除
os.mkdir(directory)
print(f"Created: {directory}")
os.rmdir(directory)
print(f"Removed: {directory}")os.rmdir() はディレクトリ内にファイルがあると失敗します。ディレクトリとその中身をすべて削除するには、まずファイルを削除する必要があります。
import os
def remove_directory_contents(directory):
"""ディレクトリ内のすべてのファイルを削除し、その後ディレクトリを削除します。
注意: ディレクトリにサブディレクトリが含まれている場合は失敗します。
"""
if not os.path.exists(directory):
print(f"Directory does not exist: {directory}")
return
# ディレクトリ内のすべてのファイルを削除
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isfile(item_path):
os.remove(item_path)
print(f"Removed file: {item_path}")
# 空になったディレクトリを削除
os.rmdir(directory)
print(f"Removed directory: {directory}")
# 使用例
test_dir = 'test_data'
os.makedirs(test_dir, exist_ok=True)
# テスト用ファイルをいくつか作成
with open(os.path.join(test_dir, 'file1.txt'), 'w') as f:
f.write('test')
with open(os.path.join(test_dir, 'file2.txt'), 'w') as f:
f.write('test')
# すべて削除
remove_directory_contents(test_dir)Output:
Removed file: test_data/file1.txt
Removed file: test_data/file2.txt
Removed directory: test_dataNote: より複雑なディレクトリ削除(サブディレクトリを含む)には、Python の shutil モジュールが提供する shutil.rmtree() を使えますが、これは現時点の範囲を超えます。
29.5.5) ファイルとディレクトリの名前変更
os.rename() はファイルやディレクトリの名前を変更したり移動したりします。
import os
# ファイル名を変更
old_name = 'draft.txt'
new_name = 'final.txt'
# テスト用ファイルを作成
with open(old_name, 'w') as f:
f.write('content')
os.rename(old_name, new_name)
print(f"Renamed '{old_name}' to '{new_name}'")ファイルを別のディレクトリへ移動することもできます。
import os
# ディレクトリとファイルを作成
os.makedirs('source', exist_ok=True)
os.makedirs('destination', exist_ok=True)
with open('source/file.txt', 'w') as f:
f.write('content')
# ファイルを別のディレクトリへ移動
os.rename('source/file.txt', 'destination/file.txt')
print("Moved file to destination directory")sys と os モジュールにより、Python プログラムはオペレーティングシステムと連携し、コマンドライン入力の受け取り、設定の読み取り、ファイルとディレクトリの管理ができるようになります。これらの機能は、単純なスクリプトを、より広いシステム環境とシームレスに統合される強力なコマンドラインツールへと変えます。