[Python]ファイルの読み書き操作

# はじめに
- 今回はpythonで基本的なファイルの読み書き操作をやってみたいと思います。
- 以下の環境で動作確認をしています。
	- python: Python 3.11.0
	- os: macOS Ventura


# ファイルの書き込み
## open関数でファイルを開く
open関数の引数にファイルパスとwモードを指定して実行し、その後closeメソッドでファイルを閉じます。今回は相対パスでカレントディレクトリに`存在しないファイル`を指定します。実行後、カレントディレクトリに該当ファイルが`新規作成`されます。
```python
f = open("sample.txt", "w")
f.write("test")
f.close()
```
`test`という文字列が該当ファイルに書き込まれています。
```sample.txt
test
```

## with文でファイルを開く
openで開いたファイルを最後に`closeし忘れると`、ファイルを`開いたまま`にしてしまい、その分のメモリを使ってしまいます。それを防ぐために、with文を使いましょう。
```python
with open("sample.txt", "w") as f:
    f.write("test")
```

上記のように書くと、インデントの中の処理が終了すると、closeを`書かなくても`ファイルをcloseしてくれます。また、`as f`の`f`というのは、開いたファイルを入れる変数なので、他の名前をつけても構いません。

## 書き換えと追記
では、ファイルに書き込む文字列を変えて、再度実行してみます。
```python
with open("sample.txt", "w") as f:
    f.write("TEST")
```
ファイルを開いてみると、中身が`TEST`に書き換えられていることがわかります。
※wモードでは、対象ファイルが存在する場合、ファイルの中身が`新しい文字列で書き換えらます`。また、書き込みの処理がなくても、`ファイルを開いた直後に中身が空になる`ので、操作ミスしないように気をつけましょう。
```sample.txt
TEST
```

書き換えるのではなく、`追記する場合`はaモードを使いましょう。
```python
with open("sample.txt", "a") as f:
    f.write("TEST")
```
```sample.txt
TESTTEST
```
## 改行を入れる
改行コードで改行を入れてみます。
```python
with open("sample.txt", "w") as f:
    f.write("TEST\nTEST\n")
```
```sample.txt
TEST
TEST

```
また、以下のように、複数行の文字列を書き込むこともできます。
```python
fruits = """\
apple
banana
orange
"""

with open("sample.txt", "w") as f:
    f.write(fruits)
```
```sample.txt
apple
banana
orange

```
※CRLFの改行コード（主にWindows）の場合は二文字（\r\n）になるので、複数行の文字列を書き込む際に、`newline="\n"`のようにopen関数のnewline引数を指定することで、改行コードがLF（\n）のテキストを作成できます。

## writelinesメソッドでリストを書き込む
writelinesメソッドで、`リスト`を書き込むことができます。
```python
fruits = ["apple", "banana", "orange"]

with open("sample.txt", "w") as f:
    f.writelines(fruits)
```
ただwritelinesメソッドは改行コードを入れてくれないので、リストの要素が`連結されたまま`書き込まれます。
```sample.txt
applebananaorange
```
リストの要素ごとに改行コードを追加せずに改行を入れたい場合は、以下のような方法があります。
```python
fruits = ["apple", "banana", "orange"]

with open("sample.txt", "w") as f:
    f.write("\n".join(fruits))
```
joinメソッドを使って、改行コードでリストの要素を連結した文字列を作成し、writeメソッドで書き込むという方法です。ただ`最後の要素の後ろには改行コードが含まれない`ので、ご注意ください。
```sample.txt
apple
banana
orange
```

## ファイルが存在しない場合のみ新規作成して書き込む
ファイルが存在しない場合のみ新規作成し書き込みたい場合は、xモードを使いましょう。ファイルが存在する場合はエラーになります。
```python
with open("new_sample.txt", "x") as f:
    f.write("test")
```
実行後、new_sample.txtというファイルが新規作成され、testという文字列が書き込まれています。
```new_sample.txt
test
```
すでに存在しているsample.txtをxモードで開いたら、以下のようにエラーが表示されることがわかるかと思います。
```python
with open("sample.txt", "x") as f:
    f.write("test")
```
```
FileExistsError: [Errno 17] File exists: 'sample.txt'
```
## print関数で書き込む
ファイルに内容を書き込む際に、writeメソッドだけでなく、print関数も使えます。print関数の引数に`file=f`のように書けば書き込み先を指定できます。
```python
with open("sample.txt", "w") as f:
    print("test from print", end="", file=f)
```
```sample.txt
test from print
```
print関数のさまざまな出力方法でそのままファイルに書き込めるという利点があります。ただwriteメソッドの方が、ファイルオブジェクトにメソッドで書き込むということがわかりやすいので、`writeメソッドの方が一般的`かと思います。

# ファイルの読み込み
## readメソッドで全文を読み込む
次にファイルの読み込み操作をやってみたいと思います。sample.txtというファイルに以下の内容が書き込まれているとします。
```sample.txt
apple
banana
orange

```
rモードでファイルを開いて、readメソッドで`ファイル全体`を読み込んでみます。
```python
with open("sample.txt", "r") as f:
    print(f.read())
```
ファイル全体を文字列として取得し、printで以下のように出力します。
```
apple
banana
orange

```
※引数モードのデフォルト値がrになっているので、rモードの場合モード引数の記述を省略することもできますが、記述してあげた方がわかりやすいかと思います。

## readメソッドで文字数を指定して読み込む 
readメソッドの引数に読み込みたい`文字数を指定`し、`ファイル内の現在位置から読み込む`ことができます。
```python
with open("sample.txt", "r") as f:
    print(f.read(1))
```
現在位置は先頭から始まるので、先頭から1文字だけ読み込むことになります。(appleのa)
```
a
```

## tellメソッドで現在位置を取得
tellメソッドで、ファイル内の`現在位置を取得`することができます。
```python
with open("sample.txt", "r") as f:
    # ファイルの先頭から移動していないので、0が返される
    print(f.tell()) # 0
    
    # 先頭から一文字を読み込む
    print(f.read(1)) # a
    
    # 一文字分読み込まれたので、現在位置が1になる
    print(f.tell()) # 1
    
    # もう一度一文字を読み込むと、二文字目のpが読み込まれる
    print(f.read(1)) # p 
```
```
0
a
1
p
```

## seekメソッドでファイル内の特定の場所に移動
seekメソッドでファイル内の`特定の場所に移動`することができます。
```python
with open("sample.txt", "r") as f:
    f.seek(6)
    print(f.read(1)) 
```
ファイルの先頭0から数えて6番目の文字が表示されます。`改行（\n）も一文字とカウント`されるので、二行目の最初の文字が表示されます。（bananaのb）
```
b
```

## readlineメソッドで一行ずつ読み込む
readlineメソッドでファイルの先頭から`一行ずつ`読み込むことができます。
```python
with open("sample.txt", "r") as f:
    while True:
        # ファイルの先頭から一行ずつ読み込む
        line = f.readline()
        print(line)
        # 読み込む行がなくなったらループを抜ける
        if not line:
            break
```
ただreadlineメソッドでは、最終行（4行目）の空行（EOF）は空文字列として返されます。
```
apple

banana

orange


```
また、ファイルオブジェクトをfor文で回しても、先頭から一行ずつ取得できます。
```python
with open("sample.txt", "r") as f:
    for line in f:
        print(line)
```
```
apple

banana

orange

```
※上記いずれの場合も改行コードを含む行を取得できます。

## readlinesメソッドでファイル全体をリストとして読み込む
readlinesメソッドでファイル全体を`行ごとに分割したリスト`として取得できます。リストの各要素に`改行コードが含まれます`。
```python
with open("sample.txt", "r") as f:
    lines = f.readlines()
print(lines)
```
```
['apple\n', 'banana\n', 'orange\n']
```

## 改行コードを取り除く
### readlineメソッドで取得した文字列
まずreadlineメソッドで取得した文字列に含まれる改行コードを取り除いてみます。以下の２つの方法があります。
```python
with open("sample.txt", "r") as f:
    # 改行コードを空文字列に置換
    line = f.readline().replace("\n", "")
print(line)
```
```
apple
```

```python
with open("sample.txt", "r") as f:
    # 文字列の末尾の改行文字を取り除く
    line = f.readline().rstrip("\n")
print(line)
```
```
apple
```

### readlinesメソッドで取得したリスト
readlinesメソッドで取得したリストに含まれる改行コードを取り除いてみます。以下の２つの方法があります。
```python
with open("sample.txt", "r") as f:
    lines = f.readlines()

# リストの内包表記でrstripで各要素の改行コードを取り除く
lines_rstrip = [line.rstrip("\n") for line in lines]
print(lines_rstrip)
```
```
['apple', 'banana', 'orange']
```

```python
with open("sample.txt", "r") as f:
    # 改行コードを含むファイル全体を文字列として取得し、
    # splitlinesで改行コードで分割されたリストを取得
	lines = f.read().splitlines()
print(lines)
```
```
['apple', 'banana', 'orange']
```

# 読み込みと書き込みを同時に行う
wモードで内容を書き込んでから、readメソッドなどで読み込むと、`io.UnsupportedOperation: not readable`というエラーになります。
読み込むためにrモードでファイルを開き直したくない場合は、以下の２つの方法があります。
## w+モード
w+モードは、wモードと同じように対象ファイルが存在しない場合は新規作成します。
また書き込んだ直後、ファイル内の現在位置が最後になってしまうので、`f.seek(0)`でファイルの先頭に移動する必要があります。

```python
with open("sample.txt", "w+") as f:
    f.write("test")
    f.seek(0)
    print(f.read())
```
```
test
```
※w+モードでは、wモードと同じように、ファイルを開いた直後にファイルの中身が空になるので、書き込みをせずにいきなり読み込もうとしても、何も読み込まれません。

## r+モード
r+モードは既存ファイルの読み書き用モードです。そのため、存在しないファイルを開こうとしたらエラーになるので、ご注意ください。

# モードの一覧
| モード | 説明                             | 補足                                                         | 
| ------ | -------------------------------- | ------------------------------------------------------------ | 
| t      | テキストモード         |  デフォルト、r, w, aと一緒に指定する                                           | 
| r      | 既存ファイルの読み込み  | デフォルト                                                                        | 
| w      | 書き込み             | ファイルが存在する場合は書き換え（ファイルを開いた直後に中身が空になる）、存在しない場合は新規作成 | 
| a      | 追記                 | ファイルが存在する場合は末尾に追記、存在しない場合は新規作成　　　　　                     |
| b      | バイナリモード        |  バイナリファイル用、r, w, aと一緒に指定する                                                       | 
| x      | 排他的書き込み        | ファイルが存在しない場合は新規作成、存在する場合はエラーになる                           |
| +      | 更新用（読み書き）     | r, w, a, xと一緒に指定する　　　　　                                              |

# 参考
- [ファイルを読み書きする | docs.python.org](https://docs.python.org/ja/3/tutorial/inputoutput.html#reading-and-writing-files)
- [組み込み関数 - open | docs.python.org](https://docs.python.org/ja/3/library/functions.html#open)
- [組み込み型 | docs.python.org](https://docs.python.org/ja/3/library/stdtypes.html?highlight=strip)
- [『シリコンバレー一流プログラマーが教える Pythonプロフェッショナル大全』 P226~234 | KADOKAWA(酒井潤 著)](https://www.kadokawa.co.jp/product/322201000299/)
- [『零基础学Python』 P238~248 | 吉林大学出版社（明日科技 著）](https://www.mingrisoft.com/Book/newDetails/id/538.html)
- [Pythonでファイルの読み込み、書き込み（作成・追記）| note.nkmk.me](https://note.nkmk.me/python-file-io-open-with/)
- [ファイル操作をマスターしよう！Pythonでの読み込み・書き込み方法を徹底解説！ | tech-teacher.jp](https://www.tech-teacher.jp/blog/python-file/)