こんにちは。
引き続きもう少しだけ、pythonについて深掘りしたいと思います。
疑問に思う度に今はAIに聞ける時代ではありますが、それだけでは理解しきれないところもやっぱりあるので、自分なりに一度まとめるのは大事だな~と感じています。
さて、この記事のサブタイトルには、
『fp の正体を追いかけたら、ファイル操作の仕組みがわかる?』
的なものをつけることにします。
以前の記事では、重複する文字が鬱陶しいという話をしました。
今回は、その時出てきた以下コードについてもう少し考えたいと思います。
fp = open("output.json", "w")
json.dump(obj=data, fp=fp)
変数のfpだけに注目すると、
fp = open("output.json", "w") # ① fpという変数を作る
json.dump(obj=data, fp=fp) # ② fp=fp の右辺は①のfp
ということで、最初に変数定義したfpと2行目で出てくる右側のfpは同じもの……
というところまでは、前回の整理で分かったことでした。
では、この変数fp には、実際何が入っているのでしょうか。
今回はその fp の正体を追いかけながら、ファイル操作の仕組みを整理します。
fp = open() の正体
fp = open("sample.txt")
シンプルな例を用意しました。
この1行、何をしているのか。
open() は「ファイルを開く」という関数です。ですので、このコードは単純に、指定したファイルを開くという動作をしているだけに見えます。
でも open() を実行したとき、ファイルそのもの("sample.txt")が変数fpに入るわけではありません。
返ってくるのは**「ファイルオブジェクト」**です。
ファイルオブジェクトとは何か。
一言で言うと、**「ファイルへの取っ手」**です。
つまりopen() が返してくれるのは、ファイルそのものではないんです。
fp = open("sample.txt")
# ↑
# 取っ手(ファイルオブジェクト)が入っている
ここで、「なんでファイルそのものじゃないの?」と疑問が出てきませんか?
わざわざ小細工しないで、ファイル本体を持ってくればオールオッケーでは?と。
でもいちいちファイルを持ってくると、メモリを無駄に使いますよね。
めっちゃ重いファイルだってあるわけなので。
でも、そのファイルを格納している引き出しの取っ手は、そんなに大きいわけではないはず。
なのでその取っ手を見て情報を得ることで、メモリを食わずに操作ができる。
わざわざ手元まで持ってこなくても、読める・書ける・閉じられる。
効率がいいわけです。
因みに取っ手は「どのファイルか(名前)」「何をするために開いたか(モード)」「今開いているか閉じているか」といった情報を持っています。ファイルそのものではなく、ファイルへのアクセス情報がまとまっているイメージです。
fp.read() はなぜ read() だけじゃダメなの?
fp = open("sample.txt")
s = fp.read()
先ほどの例に、一つ文章を加えました。
これは単純に、指定したファイルを読みたい、という操作ですが……。
「なんで fp. が必要なの?read() だけじゃダメなの?」と私は思いました。
でも確かにきちんと指定しないでread() だけだと 「どのファイルを読むの?」 がわからないかも。
read() # どのファイル?→ Pythonが困る
fp.read() # fp という取っ手がついているファイルを読む → OK
これは、fp という取っ手に対して、read という命令を出しています。
更に次の項目から、もう少し掘り下げます。
因みにですが。readだけでもだめなようで。
open()なしでread()しようとするのは、**「電話をかけていないのに、受話器に向かって喋りだす」**ようなものです。繋がっていないので、当然相手には届きません。
read() / write() / close() の使い方
ファイルオブジェクト(取っ手)には、最初からいくつかの機能が備わっています。
先ほどのread含め、見ていくことにします。
読み込む:fp.read()
fp = open("sample.txt") # 読み込みモード(記載を省略してもこのモード)
s = fp.read() # ファイルの中身を全部読み込む
print(s)
書き込む:fp.write()
fp = open("sample.txt", "w") # 書き込みモード
fp.write("こんにちは") # 文字列を書き込む
fp.close() # 忘れずに閉じる
ちなみに write() は文字列しか受け取れません。
数値を書き込みたい場合は str() で変換が必要です。
fp.write(100) # → エラー!
fp.write(str(100)) # → OK
fpって自分で名付けた箱(変数)なのに、オブジェクトとして機能が備わるんです。
これ、かなり面白くないですか?
こういうのがオブジェクト指向ってやつなのかな?
fp.read() なんて書くのも「fp という取っ手に対して命令を出す」というオブジェクト指向の形だからなのかもしれないですね。
調べてみたら、Pythonの最大の特徴は、数値も文字列も、今回出てきたファイル(fp)も、すべてが「オブジェクト」として作られている点、ということだったので、解釈はあながち間違っていないのかもしれないです。
閉じる:fp.close()
余談&当たり前の話ではありますが、少しだけ。
open() したら close() する。これはセットです。
ドアは開けたら閉めますもんね。そういうことです。
因みにclose() を忘れるとどうなるか。
実は、プログラムが終了するタイミングでOSが強制的に閉じてくれるらしいです。
なので「絶対にダメ」というわけではありません。
自動でしまってくれるなんて、かしこい。
ただ、close() した瞬間にメモリにたまっていたデータが完全にファイルへ書き込まれて保存が完了します。
忘れると「書いたつもりなのに保存されていない」という事態になることがあります。
閉じ忘れ対策としてwith構文がありますよね。ここでの説明は割愛しますが、勝手に後片付けしてくれる優秀者です。
fp = open("sample.txt", "w")
fp.write("大事なデータ")
# close() を忘れると……保存されないことがある
fp.close() # これで初めて確実に保存される
open() のモード引数
こちらも余談ですが、open()についても少し深掘りしておきたいと思います。
open() には「どんな目的でファイルを開くか」を指定できます。
| モード | 意味 |
|---|---|
r |
読み込み専用(省略するとこれ) |
w |
書き込み(ファイルがあれば上書き) |
a |
追記(ファイルの末尾に追加) |
r+ |
読み書き両用 |
b |
バイナリモード(rb や wb のように他と組み合わせる) |
b だけの指定はできません。必ず r や w と組み合わせます。
テキストモードとバイナリモードの違いは、
- テキストモード → メモ帳で開いて読めるファイル(.txt、.csv など)
- バイナリモード → 画像や音声など、文字じゃないファイル
シンプルに「メモ帳で開けるならテキストモード、そうじゃないならバイナリモード」と覚えておけば大体あってます。
まとめ:fp の正体を追いかけると
fp = open("sample.txt") # 取っ手(ファイルオブジェクト)を手に入れる
s = fp.read() # 取っ手を使ってファイルを読む
fp.close() # 取っ手を手放す(ファイルを閉じる)
| 疑問 | 答え |
|---|---|
fp に何が入っているの? |
ファイルオブジェクト(ファイルへの取っ手) |
なぜ fp.read() と書くの? |
「どのファイルを読むか」を明示するため |
次の記事では、import の使い方いろいろを整理します。
「箱ごと持ってくる」「中身だけ取り出す」「箱をひっくり返す」の違いです。
初歩的も初歩的なんですが、私のような初心者はなんだって躓くのです……。