Python

Pythonで仕事を楽にしよう~ファイルの操作~

Pythonを学んでみようと、学習を進めた際普通に読むのではなくて、会話形式でやると頭に入ってくるかも!?と思い試みたのがこちらです。
結果、会話を考えるのは面白かったのですが、Python力があがったのかと言われるとNoです

コードはコードで書いていった方がいいですね。
ですが、せっかくなのでこちら紹介したいと思います。

本記事で参考にさせていただいたのは、下記書籍になります。
Pythonを使ってあれこれ便利なことができることについて、具体的な例と共にたくさん掲載されていておススメです。
本内容の理解を深めるために、会話形式を取り入れたり自分なりにまとめ方を工夫してみました。
実際の書籍と合わせて理解を深める一助になれば幸いです!

せんぱい
せんぱい
Pythonで仕事を楽にする方法を学ぼう!シリーズ。
今回はファイルをについて学んていくよ!
今回の内容を通して分かることはこんな感じです!
  • 大量のファイル名を変更することができる!
  • 絶対パス・相対パスの意味が分かる
  • Pythonでファイル名を一気に変更する方法!
  • Pythonでテキストファイルの中身が何パターンもあるファイルを一気に作成する方法
ガゼルさん
ガゼルさん
ファイルの操作…改まって言われるとちゃんと分からないかもですね

せんぱい「今回はPythonでハードディスク上のファイルを作成・読み・書きする方法をみてみるよ」

ガゼルさん「はい!」

せんぱい「ファイルの内容を何ギガバイトにもなり得る、一つの文字列の値とみることもできるんだ」

ガゼルさん「一つの文字列の値とみる…何言ってるんですか?

せんぱい「そんな身も蓋もない…
あと、これは今回に限らずだけど、改めて実行する際に読み込む記述がさらっと抜けてたりするのでエラーになったらまたモジュールをインポートするようにしてみてね!」

ファイルとファイルパス

せんぱい「ファイルにはファイル名パスの2つの重要な構成要素があるよ」

ガゼルさん「まあ、名前は重要ですよね~で、パス?

せんぱい「パスはコンピューター上のファイルのある場所を表すよ
ファイル名のピリオド以降は拡張子と呼ばれる部分で、ファイルの種類を表す箇所になる」

ガゼルさん「ファイルの…種類?」

せんぱい「例えば、WindowsにおけるC:\はルートフォルダといって、他のすべてのフォルダを格納するフォルダを表すよ
C:\は“C:\ドライブ”とも呼んだりするね。これ以降、ルードフォルダはc:\と表すこととするね」

せんぱい「DVDやUSBなどのボリュームはOSにより扱いが異なるよ。それぞれについて、見てみるね」

  • Windowsだと
    せんぱい「-D:\やE:\のような新たなドライブ名として認識されるよ」

  • Macだと
    せんぱい「/Volumesの下に新たにフォルダができる感じになるよ」

  • Linuxだと
    せんぱい「/mnt(mountの意味)の下に新しいフォルダができる感じになるね」

ガゼルさん「OSによってこんなに違うんですね~」

せんぱい「WindowsとMacはフォルダ名やファイル名の大文字と小文字を区別しないがLinuxでは区別することに注意!」

ええ!?区切り文字の書き方ってOSによって違うの?Windowsのバックスラッシュ・MacやLinuxのスラッシュ

Windowsではフォルダ名の間をバックスラッシュ(\)で区切ってパスを書く。
一方、Macとlinuxでは区切り文字として(/)を用いる。

すべてのOSで動作するプログラムを書こうと思った場合、両方に対応するようにスクリプトを書く必要がある。
せんぱい「はい、それには便利なon.path.join()という関数があります!**
on.path.join()はパスを構成するフォルダ名とファイル名を渡せばOSに依存する区切り文字でつないだ正しいパスを文字列として返すんだ!」

ガゼルさん「ええ!?OSに依存する区切り文字に自動で…!?」

せんぱい「はい、さっそく実例を見てみましょう!」

import os
os.path.join('usr','bin','span')

せんぱい「Windowsだと」

'usr\\bin\\span'

せんぱい「みたいな結果が表示されます!」

ガゼルさん「あれ?IDLEだと¥マークで表示されてましたけど?」

せんぱい「まあ、いいじゃない。これにはOSと文字コード、IBM、マイクロソフトな深い歴史があるんだよ」

ガゼルさん「ゴクリ…!」

せんぱい「次は、リストの中のファイル名をフォルダ名に連結する例だよ」

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

せんぱい「出力結果は下記みたいな感じになるよ!」

#出力結果は 
C:\Users\asweigart\account.txt
C:\Users\asweigart\details.csv
C:\Users\asweigart\invite.docx

ガゼルさん「おお!パスとファイル名が紐づけられた感じですね!」

カレントディレクトリ

せんぱい「コンピューター上で実行しているプログラムにはカレントワーキングディレクトリなるものがある」

ガゼルさん「カレントディレクトリ?」

せんぱい「現在作業中のディレクトリって意味するだね(以降、カレントディレクトリという)
Current Working Directoryの頭文字をとって『cwd』と呼ぶこともあるよ。」

ガゼルさん「Currentが現在って意味でしたっけ?なんか、GWDみたいですね

せんぱい「ミッシェル!
ルートフォルダから始まらないファイル名やパスはカレントディレクトリの下にある、って場合が多いよね。
そこにあるから分かるやん、みたいな。」

ガゼル「今あるところにあるから、そのまま呼び出すことができるというわけですね!」

せんぱい「カレントディレクトリはos.getcwd()関数で文字列として取得でき、os.chdir()関数で変更することができるんだ。
実際に書いてみよう」

import os
os.getcwd()

せんぱい「これを処理すると、出力結果は  ’C:\Python36’ みたいになったと。
このときは、つまり、Python36というフォルダにいるということだね

ガゼルさん「カレントディレクトリ…はPython36…!」

せんぱい「そのとき、そのディレクトリにあるproject.docxというファイルを指定すると C:\Python36\project.docx が参照される」

ガゼルさん「もう、そこにいるから事前の設定がいらないんですな!

せんぱい「そう!そのとおり!
そして…!カレントディレクトリを変更する際は os.chdir() を使用するよ!」

os.chdir('C:\\Windows\\System32')
os.getcwd()

# 出力結果は 'C:\\Windows\\System32'

せんぱい「でね、カレントディレクトリをC:\Windowsに変更すると、project.docxはC:\Windows\project.docxと解釈されるようになる。
存在しないディレクトリに変更しようとするとPythonはエラーを表示させちゃうんだ

ガゼルさん「だってそりゃあ、そこにないんですもんね!」

せんぱい「そう、そこにない!」

ガゼルさん「哲学!」

せんぱい「哲学ではない!
じゃあ、実際にエラーをおこす例を見てみると…」

os.chdir('C:\\ThisFolderDoesNotExist')

せんぱい「で、エラーの結果は下記みたいな感じです!」

Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    os.chdir('C:\\ThisFolderDoesNotExist')
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。: 'C:\\ThisFolderDoesNotExist'

せんぱい「こんな感じでエラーになるよ!
ディレクトリはGUI環境ではフォルダと呼んだりするよね。
Windowsだと、作業フォルダという言葉も使ったりするね」

絶対パスと相対パス

せんぱい「んでね、ファイルの場所を示すファイルパスを指定する方法としては2通りあるよ。
それは、絶対パス相対パス

  • 絶対パス
    せんぱい「ルートフォルダ、根っこのフォルダから始まるものだね。」

  • 相対パス
    せんぱい「カレントディレクトリからはじまるもの。なんで、今いるところから見てここにあるよ!ていう感じだね」

ガゼルさん「今いるところから見て!ということですね。ウチから3件隣のジブラさん、みたいな?」

せんぱい「ジブラさん!?まあ、でも、そんな感じ!
相対パスで示す際は、.や、..というフォルダの表し方があるよ」
これらは本当のフォルダではないよ。けれど、パスの中で使用することができるんだ」

ガゼルさん「パスの中で使用することができる…!」

せんぱい「それぞれ、
.は、“現在のフォルダ”の意味。
..は、“親フォルダ”の意味」

os.makedirs()関数を用いて新しいフォルダを作成する

せんぱい「ここで、os.makedirs()関数を紹介するよ」

ガゼルさん「MAKEDIRS!」

せんぱい「この関数は新しいフォルダ(ディレクトリ)を作ることができるんだよ」

ガゼルさん「まさにメイクするんですね!」

せんぱい「んでね…」

import os
os.makedirs('C:\\delicious\\walnut\\waffles')

「上記を実行するとC:\deliciousフォルダだけではなく、その中にwalnutフォルダを、さらにC:\delicious\walnutの中にwafflesフォルダを作成するよ」

ガゼルさん「Wow!」

せんぱい「つまり、os.makedirs()は、フルパスで指定したフabsォルダに必要な中間フォルダも作成するんだ!」

ガゼルさん「おお!便利!」

os.pathモジュール

せんぱい「os.pathモジュールには、ファイル名やパスを操作するための便利な関数が沢山あるんだ。
例えば、どんなOSでも使えるようにパスを作るためにos.path.join()があるよ」

ガゼルさん「os.pathモジュール!pathカル!」

せんぱい「パスカル!?
os.pathはosモジュールの中にある為、import os を実行するだけで使用することができるよ」

ガゼルさん「モジュモジュっと…」

せんぱい「(モジュモジュ?)ファイルやフォルダ、パスを使用する際は、これから取り扱う短い例を参照するとより分かりやすいかと!
os.pathモジュールドキュメントはPythonのWebサイトの中にあるよ」

ガゼルさん「os.pathモジュモジュについては公式モジュモジュをモジュモジュする必要がある、と…」

せんぱい「全部モジュモジュになってしまってるよ!?

ちょっとここで注意!

「これから扱う例はosモジュールが必要なものがほとんどな為、IDEを再起動したら最初にimport osとしてから例を入力する必要があるよ!
インポートしておかないと“NameError:name ‘os’ is not defined”」というエラーになるからね!」

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

せんぱい「os.pathモジュールには渡されたパスが絶対パスかどうかを判定して、絶対パスや相対パスを返す関数があるんだ。
それらをみてみるよ」

ガゼルさん「絶対べきか相対べきか…それが問題だ

せんぱい「マクベス!問題なのは違いないけど…!」

  • os.path.abspath(path)
    せんぱい「引数に渡したパスの絶対パスを文字列として返す。これを呼び出すと相対パスを絶対パスに変換することができるよ」

  • os.path.isabs(path)
    せんぱい「引数が絶対パスならTrue、相対パスならFalseを返すよ」

  • os.path.relpath(path,start)
    せんぱい「startからpathへの相対パスを文字列として返すよ
    startを指定しない場合はカレントディレクトリからの相対パスを返すんだ」

ガゼルさん「ったく!すごい絶対か相対かを返しますね!」

せんぱい「そういう関数なんだからそりゃそうだよ!?
んじゃ、ちょっとこれら関数を使用した実例みてみるよ」

os.path.abspath('.')

せんぱい「.がカレントディレクトリのことを表すので、カレントディレクトリの絶対パスを表示してくれるよ」

os.path.abspath('.\\Scripts')

せんぱい「カレントディレクトリにScriptsディレクトリを追記する形で表示してくれるよ!」

os.path.isabs('.')

せんぱい「絶対パスかどうかを判定するから、これはNo!ということでFalseだね!」

os.path.isabs(os.path.abspath('.'))

せんぱい「絶対パスを返す値で絶対パスかどうか判定させているからこれはTrueだね!」

ガゼルさん「TrueLove!」

せんぱい「…!
まあ、環境により、動作結果はかわってくる為、自身のコンピューター上でもぜひ試してみて!
もう少し続いて実例をみていくよ」

ガゼルさん「サーイエッサー!」

os.path.relpath('C:\\Windows','C:\\')

せんぱい「os.path.relpath(path,start)のとおり、start(C:\)からpath(C:\Windows)への相対パスを文字列として返すよね。
C:からC:\Windowsへのパスを値として返すから、『Windows』 が返ってくる値となるよ」

os.path.relpath('C:\\Windows','C:\\spam\\eggs')

せんぱい「C:\spam\eggs から
C:\Windows へのパスだから、二階層上に移動してからの\Windowsへのアクセスになって出力結果は『..\..\Windows’』になるね~」

os.getcwd()

せんぱい「これは、カレントディレクトリの絶対パスを返してくれるから、『’C:\Python37’』みたいな値を返すよ」

dirname(path)とbasename(path)

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

ガゼルさん「これまた急にきましたね」

C:\Windows\System32\calc.exe

せんぱい「見たいな階層のファイルがあったとして」

dirname basename
C:\Windows\System32|calc.exe

せんぱい「が、それぞれ返す値となるよ。
実際にやってみるよ~」

path = 'C:\\Windows\\System32\\calc.exe'
os.path.basename(path)

#出力結果は 'calc.exe'

os.path.dirname(path)

#出力結果は 'C:\\Windows\\System32'

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

ガゼルさん「なんかサラッとタプルって出てきましたけど。タプルって何ですか!?」

せんぱい「これまで失敬!タプルはリストや辞書型といった複数の要素を管理するデータ型の一種だね!」

calc_file_path = 'C:\\Windows\\System32\\calc.exe'
os.path.split(calc_file_path)

せんぱい「はい、上記のようにos.path.splitを用いると…」

‘C:\Windows\System32′,’calc.exe’

せんぱい「みたいに、’C:\Windows\System32’の値と、’calc.exe’の値を二つの文字列を取得できる。
os.path.split()は個々のフォルダまで分割しないことに注意が必要だよ!

ガゼル「個々のフォルダまで分割しない…?」

せんぱい「そう!途中の C:\Windows\System32 の部分を分割してフォルダごとに表示させたりはできないってこと!」

ガゼル「それらはまとめてしかもってこれないってことですね!」

せんぱい「そうそう!もし、そうする場合はos.sepを用いて文字列のsplit()メソッドを呼び出すことが必要なんだ
os.sepにはプログラムを実行しているOSにおけるパス区切り文字が格納されているよ!
はい、そんなんで実例です!」

#Windowsの例
cell_file_path = 'C:\\Windows\\System32\\calc.exe'
cell_file_path.split(os.sep)

せんぱい「os.sepにはWindowsにおけるパス区切り文字が格納されていて、それで区切るから、それで区切られた値が出力されてるよ。
その結果、出力内容は  [‘C:’,’Windows’,’System32′,’calc.exe’] にたいになります!
\で区切られたわけだね!

'/usr/bin'.split(os.sep)

せんぱい「MacとLinuxではリストの最初の要素は空文字列になるよ。
出力結果は [”, ‘usr’, ‘bin’] みたいになるよ!」

ガゼルさん「MacとLinuxは似た動作になるんですね~」

せんぱい「こんな風に、os.sepとsplit()メソッドを使用するとOSに依存せず、パスを個々の要素に分解したリストを取得することができるよ~」

ガゼルさん「おお~便利~!」

ファイルサイズとフォルダ内容を調べ関数の紹介だ!

せんぱい「os.pathモジュールにはファイルサイズをバイト単位で返す関数や、指定したフォルダの中のファイルとフォルダの一覧を集める関数がある」

ガゼルさん「ファイルサイズとか気になりますもんね~」

せんぱい「はい、早速それぞれ紹介します!」

  • os.path.getsize(path)
    せんぱい「引数pathに指定したファイルのサイズをバイト単位で返すよ」

  • os.listdir(path)
    せんぱい「引数pathに指定したフォルダに含まれるファイル名とフォルダ名のリストを返すよ」
    os.pathでなくosモジュールの関数であることに注意が必要だね!

ガゼルさん「どういうことですか?」

せんぱい「うん、シンプルに言うとos.pathモジュールじゃないということだね!
じゃあ、さっそく実際にインタラクティブシェルに入力してみるよ」

os.path.getsize('C:\\Windows\\System32\\calc.exe')

せんぱい「僕のマシンで入力してみたら 出力結果は 27648 となったよ」

ガゼルさん「ファイルサイズは27,648バイトってことですね!」

os.listdir('C:\\Windows\\System32')

ガゼルさん「じゃあ、ファイル名とフォルダ名を取得できないかやってみると…!
ぎゃあ!と、とても長いリストになりました…!」

せんぱい「見ての通り、たくさんのファイルがあるということだね~
C:\Windows\System32には大量のファイルがあるけど、このディレクトリに含まれるファイルの合計を求めるには、os.path.getsize()os.listdir()を組み合わせて用いるんだ」

import os
total_size = 0
for filename in os.listdir('C:\\Windows\\System32'):
  total_size = total_size + os.path.getsize(os.path.join('C:\\Windows\\System32', filename))
print(total_size)

# 出力結果は 2094094432

せんぱい「上記はC:\Windows\System32フォルダ内の各ファイル名をループし、変数total_sizeの値に各ファイルのサイズを足していってるよ。
os.path.getsize()を呼び出す際、フォルダ名とファイル名を連結している点に注意してね」

ガゼルさん「joinしないと、意図したファイルの情報を正しく取得できないことになりますもんね」

せんぱい「で、os.path.getsize()の戻り値をtotal_sizeにfor文で足していく、と。
そして、全てのファイルをループした際C:\Windows\System32フォルダ内の合計サイズとしてtotal_sizeを表示します!」

パスが存在するか…ファイルかフォルダか判断する関数がこれだ!

せんぱい「os.pathモジュールにはパスが存在するかどうか、また、ファイルやフォルダかを判定する関数があるよ」

ガゼルさん「Pythonは存在しないパスを何かやろうとするとたいていエラーになっちゃいもすもんね。
そりゃそーだみたいな感じですが」

せんぱい「はい、ではまた関数を見ていきまーす!」

  • os.path.exitsts(path)
    せんぱい「引数に指定したファイルやフォルダが存在すればTrue、存在しなければFalseを返すよ」

ガゼルさん「exitsts…!おれの存在証明を!くれよー!」

  • os.path.isfile(path)
    せんぱい「引数に指定した先が存在し、それがファイルであればTrue、そうでなければFalseを返すよ」

  • os.path.isdir(path)
    せんぱい「引数に指定した先が存在し、それがフォルダであればTrue、そうでなければFalseを返すよ」

先輩の1ポイントメモ!!!

せんぱい「os.path.exitstsを使用すると、DVDやフラッシュメモリーが接続されているかどうかを判定することもできるよ」

os.path.exitsts('D:\\')

せんぱい「これでFalseが返ってきたら、D:\ドライブとしている外部メモリーが接続されていないということだね」

ファイルの読み込み&書き方法!Open&Writeだ!

せんぱい「これからプレーンテキストを用いたファイルの読み書きについて見ていくよ!」

ガゼルさん「レッツラGo!Googe Optimize!」

せんぱい「なぜGooge Optimizeの略である表現を…!
プレーンテキストの例としては.txtという拡張子を持つテキストファイルや、pyという拡張子を持つPythonスクリプトファイルなどがあるよ。これらはWindowsのNotePad(メモ帳)やMacのTextEditのようなアプリで開くことができる。
Pythonプログラムからもプレーンテキストの内容を簡単に読み込んで通常の文字列の値のように扱うことができるよ」

ガゼルさん「プレーンであっさり仕立てなもんだから、扱いやすいってことですね!まったくもう!」

せんぱい「なんでちょっと嫌そうなの!?
一方、バイナリファイルという種類のファイルもある。
例えば、ワードプロセッサ(Wordファイル)やスプレッドシートの文章、PDF、画像、実行ファイルなどなど。
バイナリファイルをメモ帳やTextEditで開くと無意味なぐちゃぐちゃな内容が表示されてしまう

ガゼルさん「わあ!なんだこれ!ぼくの画像をメモ帳で開いたらなんだかえらい内容が現れましたよ!」

ここにバイナリファイルを開いた例を表示してみる

せんぱい「ふふふ…それがバイナリファイルの中身だよ
バイナリファイルは各種独自の方法で開く必要がある為、生のファイルを直接読み書きすることはしない。
バイナリファイルを簡単に扱うことができるモジュールはたくさんあるんだけど、今回はshelveモジュールを使用するよ」

ガゼルさん「shelve…!これはどういう意味ですか?」

せんぱい「~を棚に上げる、載せるといった意味だよ。
まあ、バイナリファイルを開くため一旦台の上にのせる、的な?
Pythonでファイルを読み書きするには3つのステップがあるよ!」

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

1.open()関数を呼び出し、Fileオブジェクトを取得する

せんぱい「では、早速やっていってみよう!
open()関数を使ってファイルを開くためには開きたいファイルのパスを文字列として渡す必要があるよ。
渡すのは、絶対パスでも相対パスどちらでもOK!
すると、open()関数はFileオブジェクトを返してくれる。

ガゼルさん「開いたぞー!つって、何やらFileオブジェクトを返してくれるんですね!」

せんぱい「そしたら実際の流れを見る為に、hello.txtという名前のファイル名をメモ帳やTextEditを使用して作成して、保存してみて!んで、中に「Hellow world!」と入力して、どこかアクセスしやすい場所においてください!

ガゼルさん「あいあいさー!」

# Windowsユーザーの例
hello_file = open('C:\\Users\\your_home_folder\\hello.txt')

# Macユーザーの例
hello_file = open('/Users/your_home_folder/hello.txt')

# your_home_folder の部分は自身の環境に合わせて書き換える

せんぱい「上記のようにopen()を呼び出すしたら、それは『テキストファイルを読む』モード、つまり読み込みモードでファイルを開いているんだよね。
ファイルを読み込みモードで使用するとファイルからデータを読み込むだけが可能となります!」

ガゼルさん「読み込むだけ!それ以外は何もできないんですか!?」

せんぱい「うん!できない!」

ガゼルさん「なんて無力な…

せんぱい「そんな無力感いっぱいな感じでもないよ…!
Pythonのopen()関数はデフォルトモードで読み込みモードだけど、これに依存したくない!と思った場合はopen()の第2引数に’r’を渡すと、別途読み込みモードを指定することができるよ。
まあ、つまり…」

open('/Users/asweigart/hello.txt','r')
open('/Users/asweigart/hello.txt','r')

せんぱい「両方とも、同じってことだね!
open()の戻り値はFileオブジェクト、と言ったけどFileオブジェクトはコンピューター上のひとつのファイルを表すんだけど、Python上ではあくまでもリストや辞書と同様のデータ型にすぎないよ」

ガゼルさん「あくまでも、ただの型に過ぎないと!」

せんぱい「そう!上記の例ではFileオブジェクトをhello_fileという変数に一旦格納したでしょ?
そして、これらのファイルを操作するためにはhello_fileに格納されたFileオブジェクトのメソッドを呼び出すんだ!」

ファイルの内容を読み込んでいくよ!read()メソッドを使ってね!

せんぱい「ここまでで、Fileオブジェクトを取得できたから試しに内容を読み込んでみよう!
ファイル全体を一つの文字列の値として読み込むにはFileオブジェクトのread()メソッドを用いるよ。
前述のhello.txtを開いたhello_fileを使用して次のようにインタラクティブシェルに入力してみて」

ガゼルさん「はいな!」

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 fate,

せんぱい「上記のように改行し、4行にしておいてね」

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 fate,’]

ガゼルさん「おお!間に何やら\nが挟まって出力されるようになりましたね~!」

せんぱい「うん!最終行を除き、各行の末尾に改行文字\nが付いていることが分かるでしょ?
こんな風に、ひとつの巨大な文字列の値より、行ごとに分かれた文字列のリストとして扱うほうが処理しやすくなることがよくあるから覚えておくといいよ!」

ファイルを書き込む・それには書き込みモードだ!

せんぱい「Pythonではprint()関数で画面に書くみたいに、ファイルに書き込むこともできるよ」
ただ、読み込みモードで開いたファイルには書き込むことはできない

ガゼルさん「なんということでしょう…」

せんぱい「そんな悲観しないで!
ファイルに書き込むには「プレーンテキストを書く」モード(書き込むモード)、もしくは「プレーンテキストを追記する」モード(追記モード)でファイルを開く必要がある」

ガゼルさん「いろんなモード学園があるんですね

せんぱい「なに、モード学園て!?
書き込むモードは既存のファイルを上書きして最初から書き直すことになる。
変数でいうと、新しい値で置き換わってしまうことに相当する
それには、open()の第2引数に’w’を渡すと書き込みモードでファイルを開くよ」

ガゼルさん「全く入れ替わってしまうんですな!これはちょっと怖いですね!」

せんぱい「そう!だから気を付けてね!
一方、追記モードは既存のファイルの末尾にテキストを追記していく。
リストを格納した変数でいうと、変数を上書くものではなく、リストに追加していくような感じだね
追記モードでファイルを開くにはopen()の第2引数に’a’を渡すんだ」

ガゼルさん「追加のaddのa!ということなんでしょか!?」

せんぱい「おそらく!メイビー!
書き込むモードでも追記モードでも、もしopen()の第1引数に渡したファイルが存在しなければ新たに空のファイルが作成されるよ。
そして、ファイルを読み書きした後にはclose()メソッドを呼び出して閉じる、と。
ファイルを閉じた後は再度同じファイルを開くこともできるよ」

ガゼルさん「openするときに名前間違えないようにしないとですね!」

せんぱい「書き込みや追記モードのときにファイルを閉じ忘れたままプログラムが異常終了するとファイルの内容が壊れることがあるから注意してね!」

ガゼルさん「さらっと言いましたけどそれめっちゃ怖いですね…!」

せんぱい「そう、だから気を付けてね!
はい、じゃあこれまでを振り返りつつ確認してみよう~!」

# まず、bacon.txtというファイルを書き込みモードで開く準備をしていきます!
# bacon.txtというファイルは存在しない為、新たに作成されるよ!
bacon_file = open('bacon.txt','w')

#開いたファイルに対し、'Hello world!\nという文字列を渡す!
#これで、write()メソッドを呼び出すと文字列をファイルに書き込むよ
#このとき、write()は書き込まれた文字数を返します!
# 出力結果は 13、つまり13文字になるということですね!
bacon_file.write('Hello world!\n')

#それからclose()メソッドでファイルを閉じる
bacon_file.close()

# 追記モードでファイルを開くよ!aを渡してね!
bacon_file = open('bacon.txt', 'a')

# 'Bacon is not  vegetable.'という文字列を書き込むよ~ 
# 出力結果は 25になります!
bacon_file.write('Bacon is not  vegetable.')

#はい、ファイルを閉じます
bacon_file.close()

#そして、改めてファイルを開く、と
#デフォルトの読み込みモードで開きます!
bacon_file = open('bacon.txt')

#read()を呼び出しファイルの内容をcontentに格納して、ファイルを閉じまーす!
content = bacon_file.read()
bacon_file.close()

print(content)

# 出力結果は Hello World! 
# Bacon is not a vegetable
#無事、文字が追記されたのが確認できました!と!

せんぱい「writeメソッドはprint()関数のように、行末に改行文字を追加しない点に注意してね!」

shelveモジュールを用いて変数を保存する・そう、棚の上にあげるが如く…!

せんぱい「では、またちょっと戻ってshelveモジュールについてまた触れていきまーす!」

ガゼルさん「でました!shelve!」

せんぱい「shelveモジュールを用いると、Pytonプログラム中の変数をシェルフ(棚)というバイナリファイルとして保存することができるよ」

ガゼルさん「まさに棚にあげるが如くですね…!」

せんぱい「そして、ハードディスクに保存したデータは後で変数に復元することができる
shelveモジュールを使用するとプログラムに『保存』と『開く』機能を追加することができる」

ガゼルさん「『保存』と『開く』…?」

せんぱい「じゃあ、さっそく見てみよう~!」

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

せんぱい「では上記を解説していってみると…」

# まずは、なにわともあれimport shelveでインポートします!
import shelve

#shelve.open()にファイル名を渡して呼び出し、戻り値のシェルオブジェクトを変数に格納します!
#mydataには実際のファイル名をしてね!
shelf_file = shelve.open('mydata')

#ここで後で使用する変数catsの中に値をセットして...
cats = ['Zophie', 'Pooka', 'Simon']

#ここでは、shelf_fileにシェルフオブジェクトが格納されている為、shelf_file['cats'] =  cats で'cats'というキーに対応した値としてリストcatsの内容を保存している。
#シェルフの値は辞書のように変更することができる。
shelf_file['cats'] = cats

#はい、ここまでで値をセットできました!と。
#値を設定したんで、close[]メソッドを呼び出して一度ファイルを閉じます!
shelf_file.close()

せんぱい「そしてこのプログラムを実行すると…!」

  • Windowsの場合:実行するとカレントディレクトリに mydata.bak、mydata.data、mydata.dirの3つのファイルが作成される。
  • Macの場合:mydata.dbという一つのファイルが作成される。

せんぱい「上記のようなファイルが生成されます!
これらのバイナリファイルにはシェルフに保存したデータが格納されているんだ!

ガゼルさん「おお…正直何が何やらという感じです!」

せんぱい「ただ、バイナリファイルの形式は重要ではなーい!!
保存形式なんかより、shelveモジュールの機能だけを覚えておくといいぐらいだ!!

ガゼルさん「急に熱い発言ー!!」

せんぱい「このモジュールを使用すればプログラムのデータをファイルに保存する方法に悩む必要がなくなるよ」

ガゼルさん「ええ…どういうこと!?

せんぱい「まあ、見てなさいよ!
シェルフファイルを開いてデータを取り出すのにもshelveモジュールを使用するんだ
シェルフファイルは書き込みモードと読み込みモードで開き直す必要がなく、一度開けば読み書きできるよ

ガゼルさん「ええ~何その特別扱い!?」

せんぱい「次のコードを見てみて~!このコードはさっきから続きだと思ってね!」

shelf_file = shelve.open('mydata')

type(shelf_file)

shelf_file['cats']

shelf_file.close()

せんぱい「じゃあ、ソースを詳しくみていくよ~!」

#シェルフファイルを開いてデータが正しく保存されたかを調べる
shelf_file = shelve.open('mydata')
type(shelf_file)
#出力結果は <class 'shelve.DbfilenameShelf'>
#つまり、シェルブドキュメント、と言っているので無事シェルブ形式で保存できたということだね!

shelf_file['cats']
# 出力結果は ['Zophie', 'Pooka', 'Simon']
# 先ほどと同じリストを返す為、正しく保存されたことが分かる

# 最後にclose()で閉じる!と
shelf_file.close()

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

ガゼルさん「なんと…これまでの辞書風はあくまでも風だったんですね..ぼくのことは遊びだったんですね…!」

せんぱい「ええ!?何その流れ
じゃあ次はlist関数に渡すとどうなるかみてみよう~」

shelf_file = shelve.open('mydata')
list(shelf_file.keys())
#出力結果は ['cats']
#キーとして、['cats']を持っているということだね!

list(shelf_file.values())
#出力結果は [['Zophie', 'Pooka', 'Simon']]
#各値をこんな風にセットしているということだね!

shelf_file.close()

せんぱい「プレーンテキストはメモ帳やテキストエディタで読めるファイルを作成するのには便利だけど、Pythonプログラムからデータを保存したい際にはshelveモジュールを使用するのが便利だよ~」

ガゼルさん「同じ無添加でも、Pythonにやさしいshelveモジュール!」

整形した文字列を返す!pprint.pformat()関数を用いて変数を保存してみよう

せんぱい「pprint.format()関数は画面に表示せずに整形した文字列を返すんだよ」

ガゼルさん「どういうことですか?」

せんぱい「まあ、整えてくれるということだね。
整形された文字列は読みやすいだけでなく、Pythonのコードとして文法的に正しいものとなっているよ」

ガゼルさん「整えてくれるんですね!それは素敵です」

せんぱい「例えば、変数に格納された辞書があり、この変数の内容を将来の利用に備えて保存したいとするでしょ?
pprint.pformat()を用いると、『.pyファイル』に書き込める文字列を取得できるんだ」 
このファイルは独自のモジュールとしてインポートできて、その中に保存された変数をいつでも使用できるようになるよ」

ガゼルさん「おお!なんと!念能力みたい!

せんぱい「ええ~…そう~!?
まあ、実例を見てみよう~!」

import pprint
cats = [{'name': 'Zophie', 'desc': 'chubby'},{'name': 'Pooka', 'desc': 'fluffy'}]
pprint.format(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()

せんぱい「そしては次は中を詳しくみていってみるよ~」

#まず、pprint.format()を使うため、import pprintでモジュールをインポートする。
import pprint

#次に辞書のリストを作成し、変数catsに格納します!
cats = [{'name': 'Zophie', 'desc': 'chubby'},{'name': 'Pooka', 'desc': 'fluffy'}]

#インタラクティブシェルを終了後にもcatsのリストを保存しておく為に、pprint.format()を呼び出して文字列を取得!
pprint.format(cats)
# 出力結果は "[{'desc': 'chubby': 'name': 'Zophie':},{'desc': 'fluffy', 'name': 'Pooka'}]"

#catsのデータを文字列化したならそれをmyCats.pyというファイルに書き出のも簡単!
#myCats.pyはまだないから、新たに作成され、それを書き込みモードで開く!
file_obj = open('myCats.py','w')

#myCats.pyに文字を整形しつつ、catsの中身を書き込むぜ!
file_obj.write('cats = ' + pprint.pformat(cats) + '\n')
# 出力結果は 83

# はい、ファイルを閉じます!
file_obj.close()

せんぱい「pprint.format()から得られた文字列を.pyファイルに保存したなら、そのファイルは他でもインポート可能なモジュールになるよ!
Pythonのスクリプト自体が.pyという拡張子を持ったテキストファイルだから、Pythonプログラムが他のPythonのプログラムを生成することができるということだね!

ガゼルさん「ええ~…なんかウロボロス~!」

せんぱい「ニュアンス、合っているような合っていないような!生成したファイルをインポートさせる実例を見てみよう~」

#注目!さっき作成し.pyファイルだね!
import myCats
myCats.cats

# 出力結果は [{'name': 'Zophie', 'desc': 'chubby'},{'name': 'Pooka', 'desc': 'fluffy'}]
#これは中身をそのまま出力しているよ~

myCats.cats[0]
#一番目のリストを表示させます!
#出力結果は {'name': 'Zophie', 'desc': 'chubby'}

myCats.cats[0]['name']
#[0]なんで、一番最初のリストのnameに対応する値を出力するよ!
#出力結果は 'Zophie'

せんぱい「shelveモジュールを使って保存するのと比べ、.pyファイルを生成する利点はテキストファイルだからファイルの内容をテキストエディタで読んだり修正したりできることだね!
しかし、ほとんどのアプリケーションでは変数をファイルに保存したりするのにshelveモジュールを使用するほうが便利!
整数や浮動小数点、辞書のような基本的なデータ型だけならプレーンテキストとしてファイルに保存してもいいけど、例えばFileオブジェクトなどはテキストとして保存できない!」

ガゼルさん「あの、よく分からない感じになってしまうということですね~」

せんぱい「そういうこと~!」

ミッション!ランダムな問題集ファイルを作成せよ!

せんぱい「はい、ここからはこれまでのPythonを活かした実際の例を見ていきたいと思います!」

ガゼルさん「いえーい!」

せんぱい「はい!ここで!突然ですがガゼルさんが35人の生徒を持つ!社会科の先生だとします!」

ガゼルさん「ええ~すごい唐突…!

せんぱい「そのガゼル先生はこれから県庁所在地を問う選択式の問題を作成しようとしている…!
しかし!悲しいことに!生徒の中には不正を働く者がいるのです…!」

ガゼルさん「ええ…!それは悲しい…!

せんぱい「はい、そこでガゼル先生は**問題の並びをランダムにしてカンニングできないようする方法を思いつきました!」

ガゼルさん「すごい方法を思いついちゃったね!」

せんぱい「そのプログラムに持たせる仕様は下記のようになります!」

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

以上から、コードは次の動作をする必要がある

  • 県と県庁所在地を辞書に記述する
  • open()、write()、close()を呼び出して問題集と回答集を作成する
  • random.shuffle()を用いて問題や選択肢をランダムに並び替える

ガゼル「丸付けが大変そう…!」

せんぱい「で、そのコードはこんな感じになります!」

すごいマニアックな問題にする!

#! python3
# randomQuizGenerator.py - ランダム順に問題と答えを並べ問題集と解答集を作る

import random # 

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

#35個の問題集を作成していくぜ!

for quiz_num in range(35):#←ここの数字を変更すると作成したい問題集の数を変更できる!
  #問題集のファイル名はcapitalquiz<N>.txtとする!  
  #ただし、『<N>』はforループのカウンタquiz_numを使用して、ユニークな番号とするよ!
  #対応する解答集は、例えばcapitalsquiz_answer10.txt みたいに、番号が被らずどんどん作成されていくということだ!
  #ループを回すたび、'capitalsquiz{}.txt'と'capitalsquiz_answers{}.txt'の{}の部分が文字列のformat()メソッドによって(quiz_num + 1)に置き換わっていく! 
  #capitalsquiz{}.txtを書き込みモードで開きマッス!{}のところは数字が入るよ!
  quiz_file = open('capitalsquiz{}.txt'.format(quiz_num + 1),'W')

  #capitalsquiz_answer{}.txtを書き込みモードで開きマッス!{}のところは数字が入るよ!
  answer_key_file = open('capitalsquiz_answer{}.txt'.format(quiz_num + 1), 'W')

  # 生徒が記入する、問題集のヘッダーを作成していくよ!
  #名前と日付と学期を記述するところだね!
  quiz_file.write('名前:\n\n日付:\n\n学期:\n\n')

  #タイトルの記述だ!左に20文字のスペースを空けるよ!問題番号のところは今回だと35までの数字がどんどん入力されていくようにするよ!
  quiz_file.write((' '*20*) + '都道府県所在地クイズ(問題番号{})'.format(quiz_num + 1))

  #ここは2回改行しているので、一行あける感じだね!
  quiz_file.write('\n\n')

  #次は都道府県の順番をシャッフルするよ!
  #ステップ1で準備した都道府県データを入れたcapitalsからキーだけ(都道府県だけ)、リストとして取り出す!
  #
  prefectures = list(capitals.key()) 
  #それをramdom.shuffle()を使用してランダムにシャッフォー!
  ramdom.shuffle(prefectures)

    #ここから更に、ループを二つ回す!
    #では、ここから正解と誤答を取得する
    #prefecturesの中にはlist(capitals.keys())が入っているいるので、都道府県のタプルデータが格納されている
    #len(prefectures)は47になるので、question_numが47になるまで繰り返す
    #素朴な疑問でなんでrangeにする必要があるかは後で調べる!
    for question_num in range(len(prefectures)):
        #正解は辞書capitalsの値、つまりprefecturesには都道府県だけが入っている為、都道府県名だけをprefectures[question_num]で取り出して、それに対応する形で各都道府県の県庁所在地を取得する!
        #このループは、シャッフルされた都道府県リストをprefectures[0]からprefectures[46]まで繰り返し、各都道府県ごとに都道府県所在地をcorrect_answerに保存する。
        #correct_answerにはquestion_numが5のときは山形市が格納される(5は6番目になるため、それは山形県)
        correct_answer = capitals[prefectures[question_num]]

        #ここから選択肢用の誤ったデータを作成していく
        #誤答のリストを作るにはまずcapitals辞書のすべての値を複製し、
        wrong_answers = list(capitals.values())
        #次に正解を削除!
        #例えばquestion_numが5のときの正解は山形市になる
        #そのときはcapitals辞書から山形市のインデックス、つまりその5番目分だけ削除する
        del wrong_answers[wrong_answers.index(correct_answer)]

        #そして、ランダムに3つを選ぶ。この時点で正解の選択肢は削除されている
        #random.sample()関数を使用すると、簡単に選択することができる。
        #第1引数に選択元のリストを指定し、第2引数に選択肢を指定する。
        wrong_answers = random.sample(wrong_answers, 3)


       # #3つの誤答と正解をまとめて選択肢リストとする。
        answer_options = wrong_answers + [correct_answer]

        #選択肢だと、最後に正解の選択肢が並ぶこととなっている
        #このままでは常に4番目のDが正解となってしまう為、最後に選択肢リストをシャッフォーする 
        random.shuffle(answer_options)

        # 問題文と回答選択肢を問題ファイルに書いていく

        quiz_file.write('{}. {}の都道府県庁所在地は?\n'.format(question_num + 1,
            prefectures[question_num]))

        #forループで、answer_optionsリストの選択肢を0~3まで順番に表示する。     
        for i in range(4):#i in range(4)だと4回ループする
            #'ABCD'[i]という式は'ABCD'という文字列を配列とみなし、順番に'A'~'D'という値になる。
            quiz_file.write(' {}. {}\n'.format('ABCD'[i], answer_options[i]))
        quiz_file.write('\n')

        #答え合わせをするとき用に答えの選択肢をファイルに書く
        #最後に、answer_options.index(correct_answer)という式は、シャッフルされた選択肢の中から正解を見つけるもの。
        #したがって、'ABCD'[answer_options.index(correct_answer)]は、正解の選択肢記号A~Dになる。
        answer_key_file.write('{}. {}\n'.format(question_num + 1, 'ABCD'[answer_options.index(correct_answer)]))

    quiz_file.close()
    answer_key_file.close()

せんぱい「では、工程を詳しくみていってみよう~」

ガゼルさん「Yes!!」

ステップ0:スクリプトを書いていく前の心得だ!

せんぱい「このスクリプトに限らず、最初のステップではまずスクリプト全体の骨格をなぞるようにするといいよ!
なので、このスクリプトで行いたいことを書いていこう~!
randomQuizeGenerator.pyというファイルを作成し、下記の内容を入力していってみてね!」

#! python3
# randomQuizGenerator.py ランダム順に問題と答えを並べ問題集と解答集を作成するスクリプトです!
#まずは骨子を書いていってみよう!

#ランダムモジュールが必要になるからインポートを忘れない!

#問題のデータ準備する!をここにセットアップする!
#問題のデータはキーが都道府県で、値が県庁所在地になるように設定していきます!

capitals = {'北海道': '札幌市', '青森県' '青森市', …が続いていく}

#ここから35個の問題集を作成していくよ
for quiz_num in range(35):#
  # TODO: 問題集と解答集のファイルを作成する 
  # TODO: 問題集のヘッダーを書く
  # TODO: 都道府県の順番をシャッフル
  # TODO: 47都道府県をループして、それぞれ問題を作成する

せんぱい「はい、これで全体の骨子ができました、と!」

ステップ1:問題用のデータを準備だ!問題用のデータを辞書に記述するよ!

せんぱい「最初のステップではスクリプト全体の骨格を書いて、問題のデータを記述するしていくよ!
ステップ0で準備した内容に肉付けしていきまーす!」

#! python3
# randomQuizGenerator.py ランダム順に問題と答えを並べ問題集と解答集を作成する

#ランダムモジュールが必要になるからインポートを忘れない!
import random 

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

ガゼルさん「都道府県のデータ…準備するの…大変…!

せんぱい「まあ、実際には手で打ったりせず、どこからかデータは持ってくることになると思うけどね~」

ステップ2:では実際に問題集のファイルを作成!そして問題をシャッフルするよ!

せんぱい「では、実際に問題集を作成する流れをみていこう~!
ループ内のコードは35回繰り返され、一度に一つの問題集が作成される。
だから、その1回がきちんと作成されればあとは繰り返すだけだね!」

ガゼルさん「なんか…それっぽく言ってますけどどういうことですか!?

せんぱい「1パターンの問題集を作成さえできればあとはループさせるだけだから無問題ということだよ!

ガゼル「Oh~! I see!」

せんぱい「はい、では続きをみていきまーす!」

#35個の問題集を作成していくぜ!

for quiz_num in range(35):#←ここの数字を変更すると作成したい問題集の数を変更できる!
  #問題集のファイル名はcapitalquiz<N>.txtとする!  
  #ただし、『<N>』はforループのカウンタquiz_numを使用して、ユニークな番号とするよ!
  #対応する解答集は、例えばcapitalsquiz_answer10.txt みたいに、番号が被らずどんどん作成されていくということだ!
  #ループを回るたび、'capitalsquiz{}.txt'と'capitalsquiz_answers{}.txt'の{}の部分が文字列のformat()メソッドによって(quiz_num + 1)に置き換わっていくよ!と

  #capitalsquiz{}.txtを書き込みモードで開きマッス!{}のところは数字が入るよ!
  quiz_file = open('capitalsquiz{}.txt'.format(quiz_num + 1),'W')

  #capitalsquiz_answer{}.txtを書き込みモードで開きマッス!{}のところは数字が入るよ!
  answer_key_file = open('capitalsquiz_answer{}.txt'.format(quiz_num + 1), 'W')

  # 生徒が記入する、問題集のヘッダーを作成していくよ!
  #名前と日付と学期を記述するところだね!
  quiz_file.write('名前:\n\n日付:\n\n学期:\n\n')

  #タイトルの記述だ!左に20文字のスペースを空けるよ!問題番号のところは今回だと35までの数字がどんどん入力されていくようにするよ!
  quiz_file.write((' '*20*) + '都道府県所在地クイズ(問題番号{})'.format(quiz_num + 1))

  #ここは2回改行しているので、一行あける感じだね!
  quiz_file.write('\n\n')

  #次は都道府県の順番をシャッフルするよ!
  #ステップ1で準備した都道府県データを入れたcapitalsからキーだけ(都道府県だけ)、リストとして取り出す!
  #
  prefectures = list(capitals.key()) 
  #それをramdom.shuffle()を使用してランダムにシャッフォー!
  ramdom.shuffle(prefectures)

ステップ3:問題と選択肢を作成する

せんぱい「これまでで、問題用のデータや、名前をかくところを仕込めたと。
続いて、問題を作成し、問題ごとにA~Dの回答選択肢を作成していくよ」

ガゼル「」

せんぱい「47都道府県分の問題を作成するために第2のforループを用いる。
さらに、その中で選択肢を生成するために第3のループを用いる」

ガゼル「ループ&ループ&ループ!わっほい!」

# 47都道府県をループして、それぞれ問題を作成する
for question_num in range(len(prefectures)):
  # 正解と誤答を取得する
  #正解は辞書capitalsの値として記載されている為、簡単に得ることができる。
  #このループは、シャッフルされた都道府県リストをprefectures[0]からprefectures[46]まで繰り返し、各都道府県ごとに都道府県所在地をcorrect_answerに保存する。

  correct_answer = capitals[prefectures[question_num]]
  wrong_answer = list(capitals.values()) #②
  del wrong_answers[wrong_answers.index(correct_answer)] #③
  wrong_answers = random.sample(wrong_answers, 3) #④ 
  answer_options = wrong_answers + [correct_answer] #⑤
  random.shuffle(answer_options) #⑥

ステップ4:問題集と解答集ファイルに内容を書き出す

残るは問題集と解答集をファイルに書き出作業。

# TODO: 47都道府県をループして、それぞれ問題を作成する
for question_num in range(len(prefectures)):

  #先ほど記述したコードは省略
  # 問題文と解答選択肢を問題ファイルに書く

  quiz_file.write('{}. {}の都道府県庁所在地は?\n'.format(questions_num + 1,prefectures[question_num]))
  for i in range(4): #①
    quiz_file.write(' {}. {}\n'.format('ABCD[i]', answer_options[i])) #②

  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

ミッション!:マルチクリップボード

せんぱい「クリップボード、ってあるでしょ?
テキストとかをコピーしたものを保存しておく領域のことなんだけど」

ガゼルさん「メニューで、“クリップボードにコピー”って表示されるやつですね」

せんぱい「そうそう!ただ、普通は一度にひとつのテキストしか保持できないよね」

ガゼルさん「はい、そういうものだと思ってます!」

せんぱい「コピー&ペーストしたいテキストが何種類かあるときは、都度都度選択して、コピー&ペーストしなきゃいけないよね?」

ガゼルさん「そうです!」

せんぱい「はい、そこで今回は複数のテキストの断片をとっておくためのPythonプログラムを作成します!

ガゼルさん「ええ…!複数!?

せんぱい「今回作成するプログラムはmulticlipboard.pywという名前で保存する!
.pywという拡張子はPythonを実行する際にターミナルウィンドウを表示しないことを意味するよ!」

ガゼルさん「.pwdとすると、あの何か黒い画面が表示されないんですね!」

せんぱい「そうそう!そしてつくるプログラムはクリップボード上のテキストをキーワードに紐づけて保存するよ」

ガゼルさん「?どういうことですか?」

せんぱい「例えば、py mcb.pyw save spam のように実行すると、現在のクリップボードの内容をspamというキーワードに紐づけて表示する」

ガゼルさん「そのときにクリップボードに保存されている内容がspamに紐づくんですねー!」

せんぱい「そう!んで、キーワードを忘れてしまった際は、py mcb.pyw listを実行すればキーワード一覧をクリップボードにコピーできるよ!
以下、今回作成するプログラムの働きはこんな感じになります!」

  • コマンドライン引数を調べる
  • コマンドライン引数がsaveなら、クリップボードの内容をキーワードに紐づけて保存する
  • コマンドライン引数がlistならキーワード一覧をクリップボードにコピーする
  • いずれでもない場合、キーワードに対応したクリップボードにコピーする

ガゼルさん「調べて、3パターンを状況に合わせて実行するんですね~」

せんぱい「んで、必要なコードは下記みたいになります!」

  • sys.argvからコマンドライン引数を読みだす
  • クリップボードを読み書きする
  • シェルファイルを読み書きする

せんぱい「Windowsユーザーの場合、例えば、C:\mcbというディレクトリを作成してmcb.pywを置いておくとするでしょ?

@cd C:\mcb
@pythow.exe C:\mcb\mcb.pyw %**

せんぱい「そして、mcb.batというバッチファイルをC:\Windowsに作成しておくと“スタートメニュー(Win+R)の“ファイル名を指定して実行”から簡単にこのスクリプトを実行することができるよ!」

ガゼルさん「こうしておくとバシュっと呼び出せるわけですね!」

まずはなにわともあれ全体の骨格を作成する!

せんぱい「まずは基本的な設定を書いて全体の骨格をつくろう!
冒頭に、主な使い方を記しておくと例えスクリプトの実行方法を忘れてしまったとしてもコメントを見れば思いだせるから便利だよ!」

#! Python3
#mcb.pyw - クリップボードのテキストを保存・復元するスクリプトォ!-
#Usage:
#py.exe mcb.pyw save <キーワード> - クリップボードをキーワードに紐づけて保存する!- 
#py.exe mcb.pyw <キーワード> - キーワードに紐づけられたテキストをクリップボードにコピーだ!-
#py.exe mcb.pyd list -全キーワードをクリップボードにコピー!-

import shelve, pyperclip, sys 
mcb_shelf = shelve.open('mcb') 

# やること!: クリップボードの内容を保存
# やること!:キーワード一覧と、内容の読み込み

mcb_shelf.close()


せんぱい「んで、スクリプトの全体像はこんな感じになります!細かい説明文つき!!」

ガゼルさん「わーい!」

#! Python3
#mcb.pyw - クリップボードのテキストを保存・復元するスクリプトォ!-
#Usage:
#py.exe mcb.pyw save <キーワード> - クリップボードをキーワードに紐づけて保存する!- 
#py.exe mcb.pyw <キーワード> - キーワードに紐づけられたテキストをクリップボードにコピーだ!-
#py.exe mcb.pyd list -全キーワードをクリップボードにコピー!-

#shelve, pyperclip, sysモジュールをインポート!
#shelveモジュールがあるとクリップボードのテキストをシェルファイルに追加して保存できる
#クリップボードへのコピー&ペーストにはpyperclipモジュールが必要!
#コマンドライン引数を読むにはsysモジュールが必要になる
import shelve, pyperclip, sys

#シェルファイルはmcbという名前で参照するようにする!
#シェルファイルとはシェルという人とコンピューターの間に入ってやりたいことを翻訳するひとに実行をお願いするようなファイル
mcb_shelf = shelve.open('mcb')

# クリップボードの内容を保存
#コマンドライン引数が2つあって、コマンドラインの第1引数(sys.argvのリストのインデックス1の要素)が'saveなら'、
if len(sys.argv) == 3 and sys.argv[1].lower() == 'save':
  #第2引数をキーワードとして現在のクリップボードの内容を保存する!
    mcb_shelf[sys.argv[2]] = pyperclip.paste()

#コマンドライン引数が1つだけのときは、listかクリップボードに復元したいキーワードかのどちらか
elif len(sys.argv) == 2:
    # キーワード一覧と、内容の読み込み
    #コマンドライン引数がひとつだけのとき、それがlistかどうか調べる
    #lower()は全ての文字を小文字に変換する
    if sys.argv[1].lower() == 'list': 
        #もし、listの場合はシェルフにあるキーの一覧をクリップボードにコピーする!
        #テキストエディタを開いてペーストすると、その一覧を見ることができる!
        pyperclip.copy(str(list(mcb_shelf.keys())))  
    #もし、listじゃなかったらクリップボードの内容を呼び出すキーワードである!ということでクリップボードにその内容をコピーする!
    elif sys.argv[1] in mcb_shelf:
        pyperclip.copy(mcb_shelf[sys.argv[1]]) 
# シェルフを閉じーるよ!
mcb_shelf.close()

ガゼルさん「シェルフ棚にいれたり出したりですね~!」

せんぱい「このプログラムの起動方法はOSによって異なるから、それはそれでまた別途調べてみてね」

今回の記事を振り返って

せんぱい「はい、こんな感じでPythonでハードディスク上のファイルを作成&読み・書きする方法を見ていきました!」

ガゼルさん「パチパチパチーっと」

せんぱい「今回の内容を要約するとこんな感じだね!」

  • 一言でファイル、といってもそれを一まとまりの大きい文字列とみなすことができる
  • 実行中のファイルは今いる場所を意味するカレントディレクトリなる概念がある
  • ファイルパスの表し方として一点のここ!を示す絶対パス!ファイルが置かれている場所から参照できる相対パスがある
  • os.pathモジュールにはファイルのパスを操作する関数が沢山あって、新しいテキストファイルを作成して書き込むためや、既存のテキストファイルに追加するためにファイルを開くこともできる

ガゼルさん「Pythonでファイルを開いたり閉じたりするのは不思議な感じですねー!」

せんぱい「はい、こんな感じでPythonでいろいろ仕事を便利にする方法が他にも紹介されているのでぜひ、見てみてください~!」

%d人のブロガーが「いいね」をつけました。