ひとり勉強ログ

ITエンジニアの勉強したことメモ

8章 ファイルの読み書き【退屈なことはPythonにやらせよう】

8.1 ファイルとファイルパス

8.1.1 Windowsのバックスラッシュ、MacLinuxのスラッシュ

>>> import os
>>> os.path.join('user', 'bin', 'spam')
'user/bin/spam'

os.path.join()関数は、ファイル操作関数に渡して用いるような、フルパスのファイル名を作るのに便利。

my_files = ['account.txt', 'details.csv', 'invite.docx']
for filename in my_files:
    print(os.path.join('C:\\Users\\baba', filename))


C:\Users\baba\account.txt
C:\Users\baba\details.csv
C:\Users\baba\invite.docx

8.1.2 カレントディレクト

カレントディレクトリはos.getcwd()関数で文字列として取得でき、os.chdir()関数で変更することができる。

import os
os.getcwd()
'C:\\Users\\baba\\AppData\\Local\\Programs\\Python\\Python310'

os.chdir()関数でカレントディレクトリを変更することができる。

>>> import os
>>> os.getcwd()
'/Users/xxx/develop/python/automate'
>>> os.chdir('/Users/makoto/develop')
>>> os.getcwd()
'/Users/xxx/develop'

8.1.3 絶対パス相対パス

os.makedirs()関数を使って新しいフォルダを作る。

>>> import os
>>> os.makedirs('/Users/xxx/waffles')

os.makedirs()は、フルパスで指定したフォルダに必要な中間フォルダも作成してくれる。

8.2 os.pathモジュール

8.2.1 絶対パス相対パスを操作する

os.path.abspath(path) 引数に渡したパスの絶対パスを文字列として返す。

os.path.isabs(path) 引数が絶対パスならTrue、相対パスならFalseを返す。

os.path.relpath(path, start) startからpathへの相対パスを文字列として返す。

>>> os.path.abspath('.')
'/Users/makoto/develop/python/automate'
>>> os.path.isabs('.')
False
>>> os.path.isabs(os.path.abspath('.'))
True
>>> os.path.relpath('/Users/xxx/develop', '/Users/xxx')
'develop'
>>> os.path.relpath('/Users/xxx/develop', '/Users/share')
'../xxx/develop'
>>> os.getcwd()
'/Users/xxx/develop/python/automate'

os.path.dirname(path)は引数pathの最後のパス区切り記号までの文字列を返す。一方、os.path.basename(path)は最後の区切り記号より後ろを返す。

>>> path = '/Users/xxx/develop/automate/example.csv'
>>> os.path.basename(path)
'example.csv'
>>> os.path.dirname(path)
'/Users/xxx/develop/automate'

パスのdirnameとbasenameを同時に取得したいときには、os.path.split()を呼び出せば、2つの文字列のタプルを取得できる。

>>> calc_file_path = '/Users/xxx/develop/example.csv'
>>> os.path.split(calc_file_path)
('/Users/xxx/develop', 'example.csv') 

os.path.split()は個々のファイルまで分割しない。そうしたいなら、os.sepを用いて文字列のsplit()メソッドを呼び出す。os.sepにはプログラムを実行しているOSにおけるパス区切り文字が格納されている。

8.2.2 ファイルサイズとフォルダ内容を調べる

>>> os.path.getsize('/Users/xxx/develop/python/automate/example.csv')
183
>>> os.listdir('/Users/xxx/')
['.config', 'Music', '.condarc', '.docker', '.nodebrew', 'go', '.DS_Store', 'develop', '.yarnrc', '.CFUserTextEncoding', '.stCommitMsg', '.hgignore_global', '.xonshrc', '.zshrc', 'Pictures', '.zprofile', '.zsh_history', '.ipython', 'Desktop', 'Library', '.matplotlib', '.oracle_jre_usage', '.gitignore_global', '.cups', 'Public', '.tcshrc', '.anaconda', '.ssh', 'Movies', 'Applications', 'opt', '.Trash', '.gitflow_export', '.ipynb_checkpoints', '.jupyter', '.npm', 'Documents', '.BrowserLock', '.vscode', '.bash_profile', 'Downloads', '.python_history', 'first_notebook', '.continuum', '.aws', '.gitconfig', '.putty', '.viminfo', '.zsh_sessions', '.conda', '.composer']

ディレクトリに含まれるファイルの合計サイズを求める

>>> total_size = 0
>>> for filename in os.listdir('/Users/xxx/develop/python/automate'):
...   total_size = total_size + os.path.getsize(os.path.join('/Users/xxx/develop/python/automate', filename))
... 
>>> print(total_size)
255375

os.path.getsize()を呼び出すときに、os.path.join()を呼び出して、フォルダ名と現在のファイル名を連結している点に注意する。

8.2.3 パスを検査する

os.path.exists(path) 引数に指定したファイルやフォルダが存在すればTrue、存在しなければFalseを返す。

os.path.isfile(path) 引数に指定した先が存在し、それがファイルであればTrue、そうでなければFalseを返す。

os.path.isdir(path) 引数に指定した先が存在し、それがフォルダであればTrue、そうでなければFalseを返す。

>>> os.path.exists('/Users/xxx')
True
>>> os.path.exists('/Users/some_made_up_folder')
False
>>> os.path.isdir('/Users/xxx/develop')
True
>>> os.path.isfile('/Users/xxx/develop')
False
>>> os.path.isdir('/Users/xxx/develop/python/automate/chapter1.py')
False
>>> os.path.isfile('/Users/xxx/develop/python/automate/chapter1.py')
True

8.3 ファイルの読み書きの方法

Pythonでファイルの読み書きをするには3つのステップがある。

  1. open()関数を呼び出し、Fileオブジェクトを取得する。
  2. Fileオブジェクトのread()やwrite()メソッドを呼び出して読み書きする。
  3. Fileオブジェクトのclose()オブジェクトを呼び出してファイルを閉じる。

8.3.1 open()関数を用いてファイルを開く

開きたいファイルのパスを文字列として渡す。絶対パスでも相対パスでも構わない。

hello_file = open('/Users/xxx/develop/python/automate/hello.txt')

このようにopen()を呼び出すと、「テキストファイルを読む」モード、つまり読み込みモードでファイルを開く。

8.3.2 ファイルの内容を読み込む

>>> hello_content = hello_file.read()
>>> hello_content
'Hello world!!'

ファイルの中身をひとつの巨大な文字列の値だとみなせば、read()メソッドは、ファイルに格納された文字列を返すことになる。

別の方法として、readlines()というメソッドを用いれば、1行ずつの文字列のリストとしてファイルを読み込むこともできる。

例として、hello.txtと同じフォルダに、sonnet29.txtというファイルを作り、以下のように入力する。

When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my face,
>>> sonnet_file = open('sonnet29.txt')
>>> sonnet_file.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n', 'And look upon myself and curse my face,']

8.3.3 ファイルを書き込む

ファイルに書き込むには、書き込みモード、もしくは、追記モードでファイルを開く必要がある。 書き込みモードは、既存のファイルを上書きして最初から書き直すことになる。 書き込みモードでも追記モードでも、もしopen()の第1引数に渡したファイルが存在しなければ、新たに空のファイルが作成される。ファイルを読み書きした後にはclose()メソッドを呼び出して閉じる。書き込みや追記モードのときにファイルを閉じ忘れたままプログラムが異常終了すると、ファイル内容が壊れることがある。

>>> bacon_file = open('bacon.txt', 'w') // 書き込みモードで開く
>>> bacon_file.write('Hello world!\n') // 文字列をファイルに書き込む
13
>>> bacon_file.close() // ファイルを閉じる
>>> bacon_file = open('bacon.txt', 'a') // 追記モードでファイルを開く
>>> bacon_file.write('Bacon is not a vegetable.') // 文字列をファイルに書き込む
25
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt')
>>> content = bacon_file.read() // ファイル内容をcontentに読み込む
>>> bacon_file.close()
>>> print(content) // cotentを画面に表示する
Hello world!
Bacon is not a vegetable.

8.4 shelveモジュールを用いて変数を保存する

shelveモジュールを用いると、Pythonプログラム中の変数を、シェルフ(棚)というバイナリファイルとして保存することができる。ハードディスクに保存したデータは、後で変数に復元することができる。

shelveモジュールを使えば、プログラムに「保存」と「開く」機能を追加することができる。

>>> import shelve // インポート
>>> shelf_file = shelve.open('mydata')
>>> cats = ['Zophie', 'Pooka', 'Simon']
>>> shelf_file['cats'] = cats // 'cats'というキーに対応した値としてリストcatsの内容を保存
>>> shelf_file.close()

shelve.openにファイル名を渡して呼び出し、戻り値のシェルフオブジェクトを変数に格納しておく。シェルフの値は辞書のように変更することができる。 Windowsで前記のコードを実行すると、カレントディレクトリに、mydata.bak、mydata.data、mydata.dirの3つのファイルが作られる。これらのバイナリファイルには、シェルフに保存したデータが格納されている。 シェルフファイルを開いてデータを取り出すのにもshelveモジュールを用いる。 シェルフファイルは、書き込みモードと読み込みモードで開き直す必要がなく、一度開けば読み書きできる。

>>> shelf_file = shelve.open('mydata')
>>> type(shelf_file)
<class 'shelve.DbfilenameShelf'>
>>> shelf_file['cats']
['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close()```

辞書と同様に、シェルフにもkeys()とvalues()メソッドがあり、シェルフに含まれるキーと値の並びを返す。ただし、これは真のリストではなくリスト風のオブジェクトなので、真のリストが必要ならlist()関数に渡す必要がある。

shelf_file = shelve.open('mydata') list(shelf_file.keys()) ['cats'] list(shelf_file.values()) 'Zophie', 'Pooka', 'Simon' shelf_file.close()

プレーンテキストはメモ帳やTextEditで読めるファイルを作るのに便利だが、Pythonプログラムからデータを保存したいときにはshelveモジュールを使うのが便利。


### pprint.pformat()関数を用いて変数を保存する

pprint.pformat()関数は、画面に表示せずに整形した文字列を返す。整形された文字列は読みやすいだけではなく、Pythonコードとして文法的に正しいものになっている。

import pprint # モジュールをインポート cats = [{'name': 'Zophie', 'desc':'chubby'},{'name':'Pooka', 'desc': 'fluffy'}] pprint.pformat(cats) "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]" file_obj = open('myCats.py', 'w') file_obj.write('cats = ' + pprint.pformat(cats)+ '\n') 83 file_obj.close()

Pythonのスクリプト自体が.pyという拡張子を持ったテキストファイルなので、Pythonプログラムが他のPythonプログラムを生成することができる。生成したファイルをインポートしてみる。

import pprint cats = [{'name':'Zophie', 'desc':'chubby'},{'name':'Pooka', 'desc': 'fluffy'}] # 辞書リストを作成、変数catsに格納 pprint.pformat(cats) # pprint.pformat()を呼び出して文字列を取得 "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]" file_obj = open('myCats.py', 'w') file_obj.write('cats= ' + pprint.pformat(cats) + '\n') 82 file_obj.close()

import文によってインポートするモジュールも、Pythonスクリプトで記述する。pprint.pformat()から得られた文字列を,pyファイルに保存したなら、そのファイルは他でもインポート可能なモジュールになる。

Pythonのスクリプト自体が.pyという拡張子を持ったテキストファイルなので、Pythonプログラムが他のPythonプログラムを生成することができるわけである。

生成したファイルをインポートしてみる。

import myCats myCats.cats [{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}] myCats.cats[0] {'desc': 'chubby', 'name': 'Zophie'} myCats.cats[0]['name'] 'Zophie'

### プロジェクト:ランダムな問題集ファイルを作成する

* 35通りの問題集を作成する。
* 問題集は都道府県を網羅する47問の4択問題とし、問題の順番はランダムとする。
* 各問題の選択肢は、正解1つと、ランダムな誤答3つがあり、順番はランダムとする。
* 問題集は35個のテキストファイルに書き出す。
* 解答集も35個のテキストファイルに書き出す。 

! python3

randomQuizeGenerator.py

import random

問題のデータ。キーが都道府県で、値が県庁所在地

capitals = {'北海道': '札幌市', '青森県': '青森市', '岩手県': '盛岡市', '宮城県': '仙台市', '秋田県': '秋田市', '山形県': '山形市', '福島県': '福島市', '茨城県': '水戸市', '栃木県': '宇都宮市', '群馬県': '前橋市', '埼玉県': 'さいたま市', '千葉県': '千葉市', '東京都': '東京', '神奈川県': '横浜市', '新潟県': '新潟市', '富山県': '富山市', '石川県': '金沢市', '福井県': '福井市', '山梨県': '甲府市', '長野県': '長野市', '岐阜県': '岐阜市', '静岡県': '静岡市', '愛知県': '名古屋市', '三重県': '津市', '滋賀県': '大津市', '京都府': '京都市', '大阪府': '大阪市', '兵庫県': '神戸市', '奈良県': '奈良市', '和歌山県': '和歌山市', '鳥取県': '鳥取市', '島根県': '松江市', '岡山県': '岡山市', '広島県': '広島市', '山口県': '山口市', '徳島県': '徳島市', '香川県': '高松市', '愛媛県': '松山市', '高知県': '高知市', '福岡県': '福岡市', '佐賀県': '佐賀市', '長崎県': '長崎市', '熊本県': '熊本市', '大分県': '大分市', '宮崎県': '宮崎市', '鹿児島県': '鹿児島市', '沖縄県': '那覇市'}

35個の問題集を作成する

for quiz_num in range(35): # 問題集と解凍集のファイルを作成する quiz_file = open('capitalsquiz{}.txt'.format(quiz_num + 1), 'w') # answer_key_file = open('capitalsquiz_answers{}.txt'.format(quiz_num + 1), 'w') #

# 問題集のヘッダーを書く quiz_file.write('名前:\n\n日付:\n\n学期:\n\n') # 生徒が記入する問題集のヘッダーを作成 quiz_file.write*1 quiz_file.write('\n\n')

# 都道府県の順番をシャッフルする prefectures = list(capitals.keys()) random.shuffle(prefectures) # ランダムに並び替えた都道府県リストを作成する

# 47都道府県をループして、それぞれ問題を作成する for question_num in range(len(prefectures)): # 正解と誤答を取得する correct_answer = capitals[prefectures[question_num]] # 各都道府県ごとに都道府県庁所在地をcorrect_answerに保存 wrong_answers = list(capitals.values()) # capitalsの辞書のすべての値を複製 del wrong_answers[wrong_answers.index(correct_answer)] # 正解を削除 wrong_answers = random.sample(wrong_answers, 3) # ランダムに3つを選ぶ answer_options = wrong_answers + [correct_answer] # 3つの誤答と正解をまとめて選択肢リストとする random.shuffle(answer_options) # 選択肢リストをシャッフルする

# 問題文と回答選択肢を問題ファイルに書く quiz_file.write('{}. {}の都道府県庁所在地は?\n'.format(question_num + 1, prefectures[question_num])) for i in range(4): # answer_optionsリストの選択肢を0〜3まで順番に表示する quiz_file.write('{}. {}\n'.format('ABCD'[i], answer_options[i])) # 'ABCD'という文字列を配列とみなし

quiz_file.write('\n')

# 答えの選択肢をファイルに書く answer_key_file.write('{}. {}\n'.format(question_num + 1, 'ABCD'[ answer_options.index(correct_answer)])) # シャッフルされた選択肢の中から正解の番号を見つける

quiz_file.close() answer_key_file.close()

*1:' ' * 20) + '都道府県庁所在地クイズ(問題番号{})'.format(quiz_num + 1