Pythonのファイルアクセスから圧縮・解凍まで。

# はじめに
Pythonでのファイルの読み書き、フォルダ操作、ファイルパス操作、フォルダの圧縮解凍をまとめる。

# 目次
+ [ファイルの読み書き](#ファイルの読み書き)
+ [ファイルの文字コード判別](#ファイルの文字コード判別)
+ [フォルダ操作](#フォルダ操作)
+ [ファイルパス操作](#ファイルパス操作)
+ [フォルダの圧縮解凍](#フォルダの圧縮解凍)




## ファイルの読み書き
ファイルの読み書きは```open()```を使用する。```with```をセットで使うと```close()```を記載しなくてもファイルを閉じてくれるので便利。以降、withを使った方法で説明する。
```python:
# with文使わない場合
file=open('testfile.txt' ,mode='r' ,encoding='utf-8')
print(file.read())
file.close()

# with文使った場合
with open('testfile.txt' ,mode='r' ,encoding='utf-8') as file:
	print(file.read())
```` 

ファイルの読み書きのモードは```mode```、エンコーディングは```encoding```で設定できる。

### mode
**読み込み**|内容
---|---
mode='r'|読み込み。
mode='rb'|バイナリモードで読み込み。
**書き込み**|**内容**
mode='w'|書き込み。ファイルがなければ新規作成。<br>ファイルが既にある場合は上書き。
mode='a'|書き込み。ファイルがなければ新規作成。<br>ファイルが既にある場合は追記。
mode='r+'|既存ファイルの先頭に書き込み。
mode='wb'|バイナリモードで書き込み。ファイルがなければ新規作成。<br>ファイルが既にある場合は上書き。
mode='ab'|バイナリモードで書き込み。ファイルがなければ新規作成。<br>ファイルが既にある場合は追記。
mode='r+b'|バイナリモードで既存ファイルの先頭に書き込み。

### encoding
設定|内容
---|---
encoding='utf-8'|utf-8
encoding='cp932'|cp932
encoding='shift-jis'|shift-jis

### 読み書き関数
関数|内容
---|---
file.read()|ファイル全部を読み込む。
file.readline()|ファイル1ライン読み込む。
file.readlines()|ファイル全部読み込み、行ごとにリスト化。


テキストファイルの読み書きの例。
```python:text_file.py
# ファイル書き込み(mode='w')--------------------------------------
with open('testfile.txt' ,mode='w', encoding='shift-jis') as file:
	string = 'あいうえお\n1234567890\nabcdefghij\nABCDEFGHIJ\n'
	file.write(string)

# ファイル読み込み(mode='r')--------------------------------------
#read()
with open('testfile.txt' ,mode='r', encoding='shift-jis') as file:
	file_data = file.read()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'str'>
# file_data='あいうえお\n1234567890\nabcdefghij\nABCDEFGHIJ\n'

#readline()
with open('testfile.txt' ,mode='r', encoding='shift-jis') as file:
	file_data = file.readline()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'str'>
# file_data='あいうえお\n'

#readlines()
with open('testfile.txt' ,mode='r', encoding='shift-jis') as file:
	file_data = file.readlines()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'list'>
# file_data=['あいうえお\n', '1234567890\n', 'abcdefghij\n', 'ABCDEFGHIJ\n']


# ファイル追記(mode='a')------------------------------------------
with open('testfile.txt' ,mode='a', encoding='shift-jis') as file:
	string='アイウエオ\n'
	file.write(string)

with open('testfile.txt' ,mode='r', encoding='shift-jis') as file:
	file_data = file.readlines()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'list'>
# file_data=['あいうえお\n', '1234567890\n', 'abcdefghij\n', 'ABCDEFGHIJ\n', 'アイウエオ\n']

# ファイル先頭上書き(mode='r+')-----------------------------------
with open('testfile.txt' ,mode='r+', encoding='shift-jis') as file:
	string='一二三四五'
	file.write(string)

with open('testfile.txt' ,mode='r', encoding='shift-jis') as file:
	file_data = file.readlines()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'list'>
# file_data=['一二三四五\n', '1234567890\n', 'abcdefghij\n', 'ABCDEFGHIJ\n', 'アイウエオ\n']
```

バイナリファイルの読み書き。
数値文字列バイナリ変換は＜[Pythonで数値・数値文字列・バイナリ変換](https://qiita.com/tapitapi/items/e8ea6eb9b23785c42f8f)＞を参照。
```python:binary_file.py
# binaryファイル新規作成(mode='wb')-----------------------------------
with open('testfile.bin' ,mode='wb') as file:
	hex_string='0102030405060708'
	file.write(bytes.fromhex(hex_string))


# binaryファイル読み込み(mode='rb')-----------------------------------
with open('testfile.bin' ,mode='rb') as file:
	file_data = file.read()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'bytes'>
# file_data=b'\x01\x02\x03\x04\x05\x06\x07\x08'


# binaryファイル追記(mode='ab')----------------------------------------
with open('testfile.bin' ,mode='ab') as file:
	hex_string='AAAA'
	file.write(bytes.fromhex(hex_string))

with open('testfile.bin' ,mode='rb') as file:
	file_data = file.read()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'bytes'>
# file_data=b'\x01\x02\x03\x04\x05\x06\x07\x08\xaa\xaa'


# binaryファイル先頭上書き(mode='r+b')--------------------------------
with open('testfile.bin' ,mode='r+b') as file:
	hex_string='EEEE'
	file.write(bytes.fromhex(hex_string))

with open('testfile.bin' ,mode='rb') as file:
	file_data = file.read()
	print(f'# {type(file_data)=} \n# {file_data=}')
# type(file_data)=<class 'bytes'>
# file_data=b'\xee\xee\x03\x04\x05\x06\x07\x08\xaa\xaa'
```
[戻る](#目次)

## ファイルの文字コード判別
読み込むファイルとencodingがあっていないとエラーが発生するため、文字コードを事前に判定必要。```charedt.detect()```を使えば判定可能。ファイルをバイナリモードで読み込み、そのバイナリデータを```charedt.detect(<バイナリ>)```へ設定し判定する。戻り値が、辞書型で返答され、keyのencodingにタイプが格納される。エンコード判定の結果、バイナリファイルであれば、```None```が格納される。

```python:enc_type.py
import chardet

# encodingタイプを確認
test_file = 'testfile.txt'
with open(test_file ,mode='rb') as file:
	enc_type = chardet.detect(file.read())
	
print(f'# {type(enc_type)=} / {enc_type=}')
# type(enc_type)=<class 'dict'> / enc_type={'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}


# enc_type['encoding']にエンコードタイプが格納される、バイナリファイルなら None判定
if enc_type['encoding'] != None:
	with open(test_file ,mode='r',encoding=enc_type['encoding']) as file:
		print(file.read())
else:
	print(f'{test_file}はbinaryファイルです。')
```
### 参考
https://qiita.com/katryo/items/0e3b1b2c784a4a955927

[戻る](#目次)

## フォルダ操作
フォルダの作成は```os```ライブラリを、削除は```shutil```使う。```os```の```remove()```は空フォルダしか削除できない。

コマンド|内容
---|---
os.makedirs(**dir_name**, **exist_ok=True/False**)|フォルダの作成<br>**dir_name**：作成するフォルダ名<br>**exist_ok=**：Trueにするとフォルダが既に存在した場合でも処理が進む。
shutil.rmtree(**dir_name**)|配下のサブフォルダ、ファイル含め削除。削除されたものは復活できないので使う時は注意。<br>**dir_name**：削除するフォルダ
shutil.move(**src_dir**, **dst_dir**)|配下のサブフォルダ、ファイル含め移動。<br>**src_dir**：移動元フォルダ<br>**dst_dir**：移動先フォルダ
shutil.copy2(**src_file**,**dst_dir**)|ファイルをメタデータ含めコピー。<br>**src_file**：コピー元ファイル<br>**dst_dir**：コピー先フォルダ
shutil.copytree(**src_dir**,**dst_dir**)|フォルダ配下ごと再帰的にコピー。<br>**src_dir**：コピー元フォルダ<br>**dst_dir**：コピー先フォルダ

**参考**

https://docs.python.org/ja/3/library/shutil.html#module-shutil

```python:dir_test.py
import os
import shutil


# 削除
yn=input(f'test_dir フォルダを削除してよいですか? y or n')
if yn=='y':
	shutil.rmtree('test_dir')
	
# 作成
os.makedirs('test_dir',exist_ok=True)
os.makedirs('test_dir/dirA/move_dir',exist_ok=True)
os.makedirs('test_dir/dirB',exist_ok=True)
for i in range(100):
	with open(f'test_dir/dirA/move_dir/test{i:03}.txt',mode='w') as file:
		file.write('test')

# 移動 dirA→dirB
shutil.move('test_dir/dirA/move_dir', 'test_dir/dirB')

# コピー dirB→dirA
shutil.copytree('test_dir/dirB','test_dir/dirA/move_dir')

#ファイルコピー
shutil.copy2('test_dir/dirB/move_dir/test000.txt','test_dir/dirA')

```
[戻る](#目次)

## ファイルパス操作
ファイルパスの操作も```os```ライブラリを使って操作できる。

コマンド|内容
---|---
os.getcwd()|カレントフォルダを出力する。
os.walk()|パスを出力するgenerator関数。<br>カレントフォルダ、サブフォルダリスト、ファイルリストを順次出力する。
os.path.join()|パスを結合する。

現在のフォルダ配下のサブフォルダ、ファイルをリストに出力する例と、出力したファイルリストから、拡張子が.pyを探索出力する例。
```python:path_test.py
import os
import re


#現在のフォルダ-------------------------------
print(f'{os.getcwd()=}')

# サブフォルダ、ファイルリストを出力-----------
dir_list=[]  # サブフォルダリスト出力用
file_list=[] # ファイルリスト出力用
for Dir, Subdirs, Files in os.walk(os.getcwd()):
	#dir_list：サブフォルダリスト作成
	for s in Subdirs:
		dir_list.append(os.path.join(Dir,s))
	#file_list：ファイルリスト作成
	for f in Files:
		file_list.append(os.path.join(Dir,f))

# 拡張子が'.py'を出力-------------------------
for i in file_list:
	if re.search(r'.*.py$',i):
		parent =i[:i.rfind('\\')]    #スライスで最後の\より前のparentパスを出力
		reaf   =i[i.rfind('\\')+1:]  #スライスで最後の\より後のreafを出力
		print(f'# Path:{i}\n# Dir :{parent}\n# File:{reaf}')
```
[戻る](#目次)


## フォルダの圧縮解凍
tar.gzファイルに圧縮、解凍する際は、```tarfile```を使う。

コマンド|パラメータ
---|---
with tarfile.open(**filename**, 'w:gz') as tar:<br>tar.add(**comp**)|**filename**：圧縮後ファイル名<br>**comp**：圧縮対象
with tarfile.open(**filename**) as tar:<br>tar.extractall(**decomp**)|**filename**：解凍ファイル名<br>**decomp**：解凍先フォルダ名


**参考**

https://docs.python.org/ja/3/library/tarfile.html

```python:targz_test.py
import os
import tarfile

#作成('targz_test_dir'フォルダに100ファイル作成)
dir_name='targz_test_dir'
os.makedirs(dir_name,exist_ok=True)
for i in range(100):
	file_name = f'{dir_name}/test_{i:03}.txt'
	with open(file_name,mode='w') as file:
		file.write(file_name)


#tar.gz圧縮(targz_test_dir.tar.gz というファイル名で圧縮)
tar_name = 'targz_test_dir.tar.gz'
with tarfile.open(tar_name, 'w:gz') as tar:
	tar.add(dir_name)


#tar.gz解凍('decomp'フォルダに解凍)
with tarfile.open(tar_name) as tar:
  tar.extractall('decomp')
```
[戻る](#目次)

以上