Python初心者のお勉強ノート(6)標準入力と標準出力、ファイルの読み書き (original) (raw)
標準入力と標準出力
標準入力と標準出力の考え方はUnixオペレーティング・システム(OS)からきています。 プログラムを実行するとき、そのプログラム(より正確にはプロセスという)に対して、標準入力と標準出力がシステムによって用意されます。 特にプログラム起動時に何もしなければ
- 標準入力:キーボード入力
- 標準出力:画面出力
となっています。
例えば、Hello world.を画面出力するプログラムhelloworld.pyは
print("Hello world.")
でした。 print関数は標準出力に対して引数を出力しますので、デフォルトでは画面に表示されます。
標準出力の出力先はコマンドラインで変更ができます。 たとえば、ファイルに出力したければ、
py helloworld.py >hy.txt cat hy.txt Hello world.
最初のコマンドでhelloworld.pyを実行していますが、「>hy,txt」の部分が標準出力をファイルhy.txtに切り替えています。 そのために、print関数の出力先がhy.txtになり、ファイル書き込みをすることになるのです。
コマンドラインからcatコマンドでファイルの内容を表示できます(Powershell ver7やLinuxの場合)。 ファイルhy.txtの内容がHello world.であることが確認できます。
このようにプログラムが標準出力を使っていれば、コマンド起動時にその出力先を変えることができ、柔軟性が増します。
標準入出力は、OSがサポートするものなので、プログラミング言語自体が提供するものではありません。 しかし、どの言語も何らかのOS上で動くので、OSの仕組みのいくつかの部分はその言語でのプログラミングに影響します。 標準入出力は、そういうOSの影響下にある問題です。
標準入力
標準入力はデフォルトでキーボードに割り当てられています。 最初に例としてあげるのは、input関数です。
x = input("x = ")
この例では、
- 引数の文字列"x = "を画面に表示する。これが、入力を促すプロンプトになる
- キーボードから入力した文字列がxに代入される。
pythonを引数なしで起動して、input関数を試してみます。
x = input("x = ") x = 100 x '100'
最初に「x = input("x = ")」を実行すると、プロンプト「x =」が表示されます。 それに続けて、100と入力し、エンターキーを押すと、プロンプトと入力をあわせて、2行目の
x = 100
という一行になります。 次に変数xを表示すると、文字列'100'が現れます。
このようにして、ユーザと対話しながら処理をすることができます。 以前の足し算のプログラムをinut関数を使って書き直してみましょう。
x = input("x = ") y = input("y = ") print(f"x + y = {float(x)+float(y)}")
このプログラムは、xとyの2つの数字をユーザに入力させ、その和を表示する対話型プログラムです。
print関数の中の文字列はf-stringと呼ばれる文字列リテラルです。 「フォーマット済み文字列リテラル」ということもありますが、長いのでf-stringが良いでしょう。 このリテラルを詳しく説明すると長くなるので、ここでは次の2点だけ確認しておきます。
- ダブル・クォートの前にfをつける
- ダブル・クォート内の文字列に、波カッコで囲んだ式を入れることができる。式は評価され、その値を文字列にしたものが埋め込まれる
実行してみましょう。 ファイル名をadd_input.pyとします。
py add_input.py x = 100 y = 250 x + y = 350.0
標準入力も標準出力同様に入力先をコマンドから変更することができます。 これをリダイレクトといいます。 しかし、上記のinput関数はキーボード入力を想定したものであり、リダイレクトすると動作がおかしくなるでしょう。
リダイレクトの例として、標準入力からのデータをそのまま標準出力に送るプログラムstdin2stdout.pyを考えてみましょう。
import sys
for line in sys.stdin: print(line, end="")
sys.stdinは、標準入力のストリームを直接扱うことができるオブジェクトです。 これをforループと組み合わせると、変数lineに1行ずつ入れてfor文のスイートを繰り返し実行することができます。
print関数の引数で「end=""」の部分は文字列の最後に空文字列を入れる、つまり何も入れないということを意味します。 「end=""」を省略すると、改行"\n"が入ります。 文字列lineには標準入力から入ってくる改行も含まれているので、print関数で新たに改行を入れるべきではありません。
このプログラムを動かしてみます。
py stdin2stdout.py a a b b c c ^Z
同じ文字が繰り返されていますが、上がキーボード入力、下がプログラムが入力をコピーして出力したものです。 CTRL-Zで入力を終わらせることができます。Linux、Macの場合はCTRL-Dです。
次に標準入力と標準出力を繋ぎ変えてみましょう。Linuxでは(bashでは)<を使って標準入力を直接切り替えられますが、Powershell ver7ではこれはサポートされていません。 代わりに、catでファイル内容を出力し、その出力とPythonプログラムの入力をパイプで繋ぎます。
cat stdin2stdout.py | py stdin2stdout.py >stdin2stdout-copy.py cat stdin2stdout-copy.py import sys
for line in sys.stdin: print(line, end="")
1行目でコピーをしています。 2行目ではコピー先のファイルをcatで表示しています。 これでコピーが正しくできていたことがわかります。
ファイルの読み込み
標準入出力は最初からオープンされているので、簡単に使えますが、自分でファイルを新たに使う場合は、オープンとクローズが必要です。 しかし、with文を使うと、オープンのみの記述ですみ、クローズは自動的にやってくれるので楽になります。 引数で与えられたファイルを標準出力に出力するプログラムcat-p.pyを書いてみましょう。 プログラム名は標準で使われているプログラムのcatからとりました。
import sys
if len(sys.argv) != 2: print("Usage: python cat-p.py ") else: filename = sys.argv[1] with open(filename, "r", encoding="utf-8") as f: content = f.read() print(content, end="")
まず、引数の数をチェックします。 引数は1個ですが、sys.argvにはプログラム名も入るので、そのサイズは2でなければなりません。
with文のところがメインです。
- open関数でファイルを読み込みモード("r"がそれを表している)かつ、UTF-8のコーディングでオープンし、その情報をファイルオブジェクトにして変数fに代入する
- with文のスイートの中で、f.read関数でファイルを全部読み込み、それを文字列にし、それをcontent変数に代入する
- 文字列contentを標準出力にprint文で書きだす
- with文のスイートが終了し、外に出ると、ファイルは自動的にクローズされる
実行してみます。
py cat-p.py cat-p.py import sys
if len(sys.argv) != 2: print("Usage: python cat-p.py ") else: filename = sys.argv[1] with open(filename, "r", encoding="utf-8") as f: content = f.read() print(content, end="")
正しく動作していることが確認できました。
標準出力をリダイレクトすれば、このプログラムをファイルコピーのプログラムとして活用することができます。
ファイルへの書き込み
ファイルを読み込むには、はじめにopen関数を使いました。 そのときopen関数に"r"という文字列を渡したのは、「読み込み専用」という意味です。 これに対して、「書き込み専用」「追記」には"w"や"a"を指定します。 これらの"r"、"w"、"a"をモードと呼びます。
次は、ファイルに文字列を書き込むコードの例です。
with open("出力先ファイル名", "w", encoding="utf-8") as f: f.write("ファイルに書き込む文字列")
- 出力ファイル名が存在しなければ、新規作成して、文字列を書き込む
- 出力先ファイル名が存在すれば、その内容を消去し、文字列を書き込む
- with文が終わるとファイルは自動的にクローズされる
f.writeは文字列をそのまま書き込みます。printのように改行を付け加えたりはしません。
次の例は、既存のファイルに文字列を追記します。
with open("追記先ファイル名", "a", encoding="utf-8") as f: f.write("追記されるテキスト")
関数に与えた「追記先ファイル名」がディスク内に存在しない場合は、ファイルを新規作成して書き込みます。 この場合は、書き込みモードと追記モードの振る舞いは同じです。 もっとも、追記モードを使う場合は、そのファイルがすでに存在することが分かっている場合が多いですが。
ここでは、書き込みノードの例として、ファイルコピープログラムを作ってみましょう。 引数に、コピー元、コピー先の2つのファイル名を与えます。
import sys
if len(sys.argv) != 3: print("Usage: python cp-p.py ") sys.exit(1)
src = sys.argv[1] dst = sys.argv[2]
try: with open(src, "r", encoding="utf-8") as src_file: content = src_file.read()
with open(dst, "w", encoding="utf-8") as dst_file:
dst_file.write(content)
except FileNotFoundError: print(f"Error: The file '{src}' was not found.") except IOError: print("Error: An error occurred during file read/write.")
- sys.exit(1)関数はプログラムを終了してコマンドに戻る。このとき、1という状態をシステム側に通知している。この数字はOS依存だが、通常0は「正常終了」を表し、それ以外は「異常終了(正常にプログラムが完了しなかった)」を表す。この関数を使うことにより、引数の数が間違っていた場合を「即時終了」として除き、残りのプログラムと区別でき、可読性を上げている
- ファイルの読み書きは実行時にエラーが起こる可能性があるので、例外処理を使っている。例外処理の内容は、読み込み時にファイルが見つからなかった場合(FileNotFoundError)とそれ以外の入出力エラー(IOError)に分けている
試してみます。
py cp-p.py cp-p.py cp-p2.py cat cp-p2.py import sys
if len(sys.argv) != 3: print("Usage: python cp-p.py ") else: src = sys.argv[1] dst = sys.argv[2]
try:
with open(src, "r", encoding="utf-8") as src_file:
content = src_file.read()
with open(dst, "w", encoding="utf-8") as dst_file:
dst_file.write(content)
except FileNotFoundError:
print(f"Error: The file '{src}' was not found.")
except IOError:
print("Error: An error occurred during file read/write.")
コピーの元は、このプログラム自身(cp-p.py)です。 プログラムの実行後、catコマンドでコピー先のファイルを表示しました。 コピーができていたことが確認できました。
最後に、文字コードについて補足しておきます。 現在、事実上の標準文字コードはUTF-8です。 しかし、UTF-8は比較的新しく、その前は様々な文字コードが使われていました。 open関数で文字コードを省略すると、システムの標準文字コードが採用されます。LinuxやMacではそれがUTF-8なので何の問題もありませんが、WindowsではCP932というShift-JISの拡張である可能性が高いです。 現在ではUTF-8が主流で、それにしておけばまず混乱はありません。 したがって、open関数には必ず encoding="utf-8" をつけるようにしましょう。