質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.34%

python:for文後の処理について、実行されない箇所があるのは何故なのでしょうか?

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 1,770
退会済みユーザー

退会済みユーザー

pythonで以下のようなファイルを作成しました(不明な点を簡略化するために作成したので、無理矢理感がありますが、その点はご了承下さい)。

import csv
import time

lists = ["red", "blue", "yellow"]
for i in lists:
    csv_file = open('color.csv', 'a+', newline='')
    writer = csv.writer(csv_file)
    writer.writerow(i)
    print(i + "ファイル投入完了")
    time.sleep(3)


fn = 'color.csv'

with open(fn, "r") as f:
    s = f.read()
s = s.replace("r,e,d","赤")
s = s.replace("b,l,u,e","青")
s = s.replace("y,e,l,l,o,w","黄色")

with open(fn, "w") as f:
    f.write(s)

上記のファイルは、listsに格納された要素を一つずつ取り出し、csvファイルに格納し、カンマを取るために文字を置き換える処理をしているのですが、何故か最後に取り出した、「yellow」だけが、y,e,l,l,o,wのままです。

赤
青
y,e,l,l,o,w

私の中ではfor文が完結してから、ファイルの置き換え及び作成が実行されるという考えなのですが、実際にはどのような順序で処理がされているのでしょうか?
また、解決方法があれば教えて頂きたいです。

どなたかアドバイスお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

openしたものはcloseしましょう。


質問文みたいになる理由の説明。

処理系の実装、OS側でのバッファリングなどに依存する話だし、いろいろな背景知識がないとわからないので、あまり深く考えない方が良いのですが……

まず前提として、ファイルにwriteしたタイミング(csv.writerを介しても同じこと)でファイルへの書き込みが完了する訳ではありません。内部的に都合のいいタイミングで書き込まれるんです。

closeする、あるいは単にflushすることで、そのタイミングで書き出すことをほぼ強制できます(closeしたときにはflushがファイルを閉じる前に呼ばれます。また、closeしたとしてもOS側で行われるバッファリングによってタイミングのズレは生じる可能性があります。拙記事:ファイルオブジェクトのcloseはflushも行う。確実にしたければfsync - 静かなる名辞)。

以上を踏まえて、コードを見ていきましょう。

まずこの部分について。

import csv
import time

lists = ["red", "blue", "yellow"]
for i in lists:
    csv_file = open('color.csv', 'a+', newline='')
    writer = csv.writer(csv_file)
    writer.writerow(i)
    print(i + "ファイル投入完了")
    time.sleep(3)

csv_filewriterという変数名を毎回使いまわしていますね。ということは、"red"のときと"blue"のときのファイルオブジェクト(型としては_io.TextIOWrapper)csv.writerオブジェクトは他に参照している変数がないので、再代入のタイミングで参照カウントが0になり、メモリ上から消滅します。

といってもいきなり消滅する訳ではなく、オブジェクト固有のメモリ上から消える前に行われる処理があります(いわゆるデストラクタ)。ここでcloseが呼ばれる実装になっているので、このタイミングで書き出されます。

明示的にファイルを閉じなかった場合は、いつかは Python のガベージコレクタがそのファイルオブジェクトを破棄し開かれいていたファイルを閉じますが、しばらくはファイルが開かれたままでいる可能性があります。
7. 入力と出力 — Python 3.7.4 ドキュメント | 7.2. ファイルを読み書きする

で、よくよく考えてみてほしいのですが、"yellow"のときに最後に代入されたファイルオブジェクトcsv.writerオブジェクトは、特に再代入されないので、そのまま残ります。

なので、ファイルは「開きっぱなし」です。そのまま下の部分が実行されます。

fn = 'color.csv'

with open(fn, "r") as f:
    s = f.read()

この下でprint(s)とかやるとわかりますが、"y,e,l,l,o,w"は書き込まれていません。これはバッファリングされていて、そのうち書き込まれるというものです。この状態で

s = s.replace("r,e,d","赤")
s = s.replace("b,l,u,e","青")
s = s.replace("y,e,l,l,o,w","黄色")

with open(fn, "w") as f:
    f.write(s)

が実行されますが、"y,e,l,l,o,w"なんて最初からないので、ファイルの中身は

赤
青

になります。

これですべての処理が終わってPythonプロセスが正常終了するのですが、その前に残っているオブジェクトはすべて破棄され、ここでデストラクタが呼ばれます。「開きっぱなし」にしていたファイルオブジェクトも破棄され、そこでデストラクタが呼ばれるのでやっと"y,e,l,l,o,w"が書き込まれる。

……という流れです。


ついでに。

文字列の書き込みならcsv.writerを使う必要はありません。というか使うだけ無駄です。f.write(string + "\n")とかしてください。

ループが回る度にファイルを開き直すと効率と見た目が悪いです。普通はループの外で開くでしょう。

lst = ["red", "blue", "yellow"]
filename = "color.csv"
with open(filename, "w") as f:
    for x in lst:
        f.write(x + "\n")

書きたいものが赤青黄色なら最初からこの3行を書けば良いので、上の処理は必要ありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/12 05:47

    詳細なアドバイスありがとうございます。
    大変勉強になると共に、正直今の自分の力量ではまだまだ理解が足りない部分もあります。
    hayataka2049さんのような理論的且つ効率的で無駄の無いコードを書けるよう精進して参りますので、今後もアドバイスよろしくお願いします。

    キャンセル

  • 2019/08/12 06:54

    本来は知っておくべきことですが、この辺のことを書いている「Pythonの教材」はほとんどないでしょうから、初心者の方が理解できなくても仕方ありません(バッファリングや参照カウンタをちゃんと説明している入門書がどれだけあることか……)。
    ただ、「何言ってるんだこいつ」と思った時点で調べるという努力はしてください。わけわからないキーワードが出てきたら調べて、半可通でもいいからわかるようになれるかどうかが、伸びる人とそうでない人の分かれ目のように思います。

    キャンセル

checkベストアンサー

0

オープンした後はクローズするクセをつけるのが良いですよ。

for i in lists:
    csv_file = open('color.csv', 'a+', newline='')
    writer = csv.writer(csv_file)
    writer.writerow(i)
    csv_file.close()
    print(i + "ファイル投入完了")
    time.sleep(3)


あるいは

for i in lists:
    with open('color.csv', 'a+', newline='') as csv_file:
        writer = csv.writer(csv_file)
        writer.writerow(i)
        print(i + "ファイル投入完了")
        time.sleep(3)


こちらの方がおすすめです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/12 02:13

    実例までありがとうございました。
    ちょっと気になったのですが、1回1回closeするのではなく、「fn = 'color.csv'」の直前(for文が終了した時点)でcsv_file.close()するのはNGなのでしょうか?

    また、2つある例文のうち、後者がおすすめなのは前者と比較してどのような理由からなのでしょうか?

    キャンセル

  • 2019/08/12 02:28

    今回の例では、最後に close するだけでも動きそうですね。
    とはいえ open と close は1セットにするのが無難だと思います。

    後者の例ですと、with 句の外に出た時点で自動的に close されるため、
    close を書き忘れる心配をしなくてもよくなります。

    キャンセル

  • 2019/08/12 02:30

    そう言う事だったのですね!
    とても勉強になりました。
    ありがとうございました!

    キャンセル

0

書き終わってから読むまでの間に、csv_file.close()しましょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/12 02:30

    ズバリおっしゃる通りでした!
    素早い回答ありがとうございました!

    キャンセル

  • 2019/08/12 08:17

    後半で、with openが使えているのに前半で使えてないのは、それぞれ別のところからコピペしたものではないかと思います。
    他人のコードを参考にするのは良いのですが、「動けば良い」ではなくて「自分が成長する」つもりがあるのであれば、意味を理解するようにしましょう。

    前半も、ループごとにopenしていますが、ループの外側で1度だけオープンするのが良いと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る