参考元:
http://diveintopython3-ja.rdy.jp/comprehensions.html
Dive Into Python3の3章。3は好きな数字。内包表記とやらを学ぶらしい。
❝私たちの想像力は、フィクションにおけるように、存在しないものを想像する時に発揮されるのではありません。
むしろ、現にそこにあるものをよく理解しようとする時にこそ想像力は大きく引き伸ばされるのです。❞
— リチャード・ファインマン
ファインマン好き。ご冗談でしょうファインマンさん思い出す。
飛び込む
言語はそれぞれ独自に何か複雑なものを意図的にシンプルにしようと設計されている。何かは言語ごとに違うよ。
この章では、リスト内包表記・辞書内包表記・集合内包表記を教わる。加えて、ファイルシステムの操作を支援する2つのモジュールを紹介してもらう。
3.2 ファイルとディレクトリを扱う
python3にはos
というモジュールが付属している。Operating Systemの略だ。os
モジュールにはローカルディレクトリ・ファイル・プロセス・環境変数の情報を取得するための関数が山盛り。Pythonは、サポートしてるオペレーティングシステムのどれに対しても統一されたAPIを提供できるよう最善を尽くしてるから、どんな環境でも最小限の環境依存コードでプログラムで動く。
3.2.1 現在の作業ディレクトリ
- exampleフォルダにあるモジュールを1つインポートする
- そのモジュールから関数を呼び出す
- 結果について説明
Dive into python第1章で僕は過去に失敗してる。だからとimportErrorを経験済みだから対処方法はわかってる。
・(a) importしたいモジュールのある場所をsys.pathリストに追加する
・(b) importしたいモジュールのあるディレクトリへ移動する。
のどっちかだ。
>>> import os ①
>>> print(os.getcwd()) ②
/Users/badapple330/Desktop/pythoncode
>>> os.chdir("/Users/badapple330/Desktop") ③
>>> print(os.getcwd()) ④
/Users/badapple330/Desktop
- osモジュールはPythonに付属しているから、いつでもどこでもインポートできる
- 現在の作業ディレクトリを知るには
os.getcwd()
関数を使う。python3をterminalで起動する前に作業ディレクトリをcd
とかで移動してるともちろん作業ディレクトリはデフォルトから変わってる。初期値はpythonをインストールした場所に依存。 - 現在の作業ディレクトリを変更するには、
os.chdir()
関数を使う -
os.chdir()
関数を呼び出す際に、OSごとに / が \ だったり違ってもpythonは差異を減らす努力をしてくれるらしい。
3.2.2 ファイル名とディレクトリ名を扱う
ディレクトリの話。os.path
モジュールに触れる。os.path
にはファイル名とディレクトリ名を操作するための関数が入ってる。
>>> import os
>>> print(os.path.join("/Users/badapple330/Desktop/pythoncode", "humansize.py")) ①
/Users/badapple330/Desktop/pythoncode/humansize.py
>>> print(os.path.join("/Users/badapple330/Desktop/pythoncode/", "humansize.py")) ②
/Users/badapple330/Desktop/pythoncode/humansize.py
>>> print(os.path.expanduser("~")) ③
/Users/badapple330
>>> print(os.path.join(os.path.expanduser("~"), "Desktop", "pythoncode", "humansize.py")) ④
/Users/badapple330/Desktop/pythoncode/humansize.py
-
os.path.join()
関数は、1つ以上のパス名の断片をもとにパス名を組み立てる。今回は、文字列を単純に結合させている - 1はpythoncode/ か2はpythoncode で/なし。/がなくて危うく変に結合されてしまいそうでも,
os.path.join()
がファイル名の前にスラッシュを追加してくれる。 -
os.path.expanduser()
は、現在のユーザーのホームディレクトリを~(チルダ)で表現し、それまでのpathの展開を行う。 -
os.path.join()
関数は引数をいくらでも受け取れる。ディレクトリとファルダ名とか羅列すれば行きたい階層用pathを作ってくれる。pythonではaddSlashIfNecessary()なんていらないのすごい嬉しいって先生言うてる。
os.path
モジュールには、フルパス名・ディレクトリ名・ファイル名を分解するための関数も入ってる。
・フルパスとは(=絶対パス): pathを利用して、目的のファイルやフォルダーの位置を参照するときの表現。root(根っこ)から目的地のディレクトリまでを順々に記述したパスのこと。ディレクトリ群と言い換えられる?
例)/Users/badapple330/Desktop/pythoncode/humansize.py
・ディレクトリとは:ファイルをグループ化するためのファイル。ファイルじゃん。階層的フォルダの各階層を指してる? Directory, dir
例)badapple330, Desktop, pythoncode
・ファイルとは:データのまとまり。データ名.拡張子
例)humansize.py
>>> pathname = "/Users/badapple330/Desktop/pythoncode/humansize.py"
>>> os.path.split(pathname) ①
('/Users/badapple330/Desktop/pythoncode', 'humansize.py')
>>> (dirname, filename) = os.path.split(pathname) ②
>>> dirname ③
'/Users/badapple330/Desktop/pythoncode'
>>> filename ④
'humansize.py'
>>> (shortname, extension) = os.path.splitext(filename) ⑤
>>> shortname
'humansize'
>>> extension
'.py'
-
split()
関数は、フルパス名を分解して、pathとfile名を含むタプルを返す。pathはrootとディレクトリ群 - 多値代入。関数から複数の戻り値を返している。
split()
関数の戻り値を、2つの変数からなるタプルへ代入している。変数名dirname, filenameは対応する要素の値として戻り値を受け取っている。
Example:
>>> a = "oppai siri"
>>> [x, y] = a.split(" ")
>>> x
'oppai'
>>> y
'siri'
# 受け皿はタプルじゃなくても受け取れる。[x, y]はリスト。多分集合とかはキツイだろな。順序がないってことだし値が行方不明になりそう?
- 1つ目の変数dirnameは、
os.path.split()
関数から返されたタプルの1つ目の要素を受け取る - 2つ目の変数filenameは
os.path.split()
関数から返されたタプルの2つ目の要素を受け取る -
os.path
にはos.path.splitext()
関数がある。ファイル名を分解して、ファイル名と拡張子からなるタプルを返す。もちろん多値代入できますとも。
3.2.3 ディレクトリの内容を見る
glob
モジュール。python標準ライブラリに含まれるツールの1つ。ディレクトリの内容をプログラムから簡単に取得させてくれる。このモジュールではワイルドカードの一種を使用するって。
ワイルドカードとは?
A.共通の文字や文字列を一括して指定するための特別な文字。カードゲームのjokerはどんなカードにでもなれる。
>>> os.chdir("/Users/badapple330/Desktop/pythoncode")
>>> glob.glob("pythoncode/*.py") ①
['pythoncode/humansizeのコピー5.py', 'pythoncode/humansizeのコピー4.py', 'pythoncode/humansize.py', 'pythoncode/humansizeのコピー.py', 'pythoncode/humansizeのコピー3.py', 'pythoncode/humansizeのコピー2.py']
>>> os.chdir("pythoncode/") ②
>>> glob.glob("*size*.py") ③
['humansizeのコピー5.py', 'humansizeのコピー4.py', 'humansize.py', 'humansizeのコピー.py', 'humansizeのコピー3.py', 'humansizeのコピー2.py']
- globモジュールはワイルドカードを受け取って、ワイルドカードにマッチするすべてのファイルとディレクトリのパスを返す。今回はディレクトリパスに
*.py
を加えたもの。pythoncodeディレクトリにあるすべての.py
ファイルにまっちする - 作業ディレクトリをpythoncodeにうつした。相対パスも
os.chdir()
は受け取れる
相対パスとは?
A. 現在の作業ディレクトリから目的のファイルや、フォルダまでの道筋を記述するpath -
glob()
の中でワイルドカード*を複数回使うことも出来る。*size*.py
の場合、.pyの拡張子で終わり、testという単語をファイル名のどこかに含むファイルが探される。便利
3.2.4 ファイルのメタデータを取得する
現代的なファイルシステムは、各ファイルごとにメタデータを格納している。pythonは、これらのメタデータにアクセスするための単一のAPIを用意している。ファイル名さえあれば、ファイルを開かずにメタデータにアクセスできる。
Q.メタデータとは?:
A. 作成日・最終更新日・ファイルサイズなど。
Q.APIとは?:
A.Application Program Interface。ソフトウェアの機能を共有すること。微妙…。とりあえず、機能やプログラムと理解しとく。
Q.Interfaceとは?インターフェイス:境界面。接点。
A.プログラム同士をつなぐもののことか。プログラムと別のものをつなぐもの。規格や手段やモノ。
cwd:current working directory
>>> import os
>>> print(os.getcwd())
/Users/badapple330/Desktop
>>> os.chdir("pythoncode/")
>>> print(os.getcwd()) ①
/Users/badapple330/Desktop/pythoncode
>>> metadata = os.stat("humansize.py") ②
>>> metadata.st_mtime ③
1518243422.0957828
>>> import time ④
>>> time.localtime(metadata.st_mtime) ⑤
time.struct_time(tm_year=2018, tm_mon=2, tm_mday=10, tm_hour=15, tm_min=17, tm_sec=2, tm_wday=5, tm_yday=41, tm_isdst=0)
- 現在の作業ディレクトリはcurrent working Directory()略して,
cwd()
で分かる -
humansize.py
はpythoncodeディレクトリにあるファイル。os.stat()
関数を呼び出すと、そのファイルに関するメタデータを含んだオブジェクトが返される - st_mtimeは最終更新日。ただこの形式じゃわけわかめ。これはエポック時と呼ばれ、1970年1月1日からの経過秒数。1518243422.0957828秒って書くとかっこいい。
- timeモジュールはpython標準ライブラリに含まれている。時間表現を別の表現に変換するための関数や、時刻の値を文字列にする関数、タイムゾーン(地域ごとの時間)をいじる関数など、時間に関する関数が入ってる
-
time.location()
関数は、(metadata.st_mtime
に入ってるos.stat(humansize.py)
の戻り値の、)エポック時からの経過秒数。これを年、月、日、時、分、秒などで構成される便利な構造へ変換する。2018/2/10/15:17分ごろに最終更新したみたい。
>>> metadata.st_size ①
2528
>>> import humansize ②
>>> humansize.approximate_size(metadata.st_size)
'2.5 KiB'
-
on.stat()
関数は、st_sizeプロパティの中でファイルのサイズも返す。humansize.pyのファイルサイズは2528byteだ - st_sizeプロパティをbyteの値として関数に渡すこともできる。2.5KiBだと。
3.2.5 絶対パス名を検索する
glob.glob()
関数が相対パスを返すのは前述の通り。"humansize.py"とか"pythoncode/humansize.py"みたいな相対パスだった。しかし、絶対パス名(フルパス)を作りたいのなら、os.path.realpath()
関数が必要だ。
>>> import os
>>> print(os.getcwd())
/Users/badapple330/Desktop/pythoncode
>>> print(os.path.realpath("humansize.py"))
/Users/badapple330/Desktop/pythoncode/humansize.py
>>> print(os.path.realpath("pythoncode/")
/Users/badapple330/Desktop/pythoncode/pythoncode
1.単純にos.path.realpath()
関数の引数にファイル名やあるいはディレクトリを文字列として入れればいい。
3.3 リスト内包表記
リスト内包表記とは、「リストの各要素に関数を適用することで、リストを別のリストにマッピング」する方法のこと。
Q.マッピング?
A.とあるアレと別のコレを関連付けること。だそうで
>>> a_list = [1, 9, 8 ,100]
>>> [elem * 2 for elem in a_list] ①
[2, 18, 16, 200]
>>> a_list ②
[1, 9, 8, 100]
>>> a_list = [elem * 2 for elem in a_list] ③
>>> a_list
[2, 18, 16, 200]
1. a_listはマッピングの元になるリスト。for文の範囲でa_listを使っている。右から左へ見ればいいって。a_listの中身が一時変数elemn代入。Pythonは次にelem * 2を適用。戻り値をリストに追加する。
2. リスト内包表記は新しいリストを作るから、元リストには無影響。
3. マッピングする変数(この場合a_list)にリスト内包表記の結界を代入できる。新しいリストをメモリ上に作っておき、リスト内包表記が処理が終わり次第、結果を元の変数に入れるから。
リスト内包表記:
[関数(一時変数) for 一時変数 in マッピングするlist]
[一時変数 for 一時変数 in マッピングするlist]
リストの中身に対して一括で関数を適用できる。for文の記述が短くなる。
リスト内包表記の中では、pythonのすべての式が使える。ファイルやディレクトリを操作するosモジュールも使える。
>>> import os, glob
>>> glob.glob("*.py") ①
['humansizeのコピー5.py', 'humansizeのコピー4.py', 'humansize.py', 'humansizeのコピー.py', 'humansizeのコピー3.py', 'humansizeのコピー2.py']
>>> [os.path.realpath(f) for f in glob.glob("*.py")] ②
['/Users/badapple330/Desktop/pythoncode/humansizeのコピー5.py', '/Users/badapple330/Desktop/pythoncode/humansizeのコピー4.py', '/Users/badapple330/Desktop/pythoncode/humansize.py', '/Users/badapple330/Desktop/pythoncode/humansizeのコピー.py', '/Users/badapple330/Desktop/pythoncode/humansizeのコピー3.py', '/Users/badapple330/Desktop/pythoncode/humansizeのコピー2.py']
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 2500] ③
['humansizeのコピー5.py', 'humansizeのコピー4.py', 'humansize.py', 'humansizeのコピー.py', 'humansizeのコピー3.py', 'humansizeのコピー2.py']
- 現在の作業ディレクトリに含まれるすべての.pyファイルのリストを返す
- リスト内包表記は.pyリストをうけとり、フルパス名のリストに変換
- リスト内包表記によって、要素のフィルタリングもできる。リスト内包表記の最後にif節を付け加えることができる。if節の中身は、リストの各要素に対して評価される。各要素がTrueと評価された場合に出力に加わる。今回はファイルのバイトが2500以上かどうかを評価している。
今までに説明したリスト内包表記は、単純な式だけ -定数による蒸散、1つの関数の呼び出し、元のリストの要素のフィルタリング- を使用していた。しかし、リスト内包表記はいくらでも複雑にできる。
>>> import os, glob ①
>>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob("*.py")] ②
[(2528, '/Users/badapple330/Desktop/pythoncode/humansizeのコピー5.py'), (2528, '/Users/badapple330/Desktop/pythoncode/humansizeのコピー4.py'), (2528, '/Users/badapple330/Desktop/pythoncode/humansize.py'), (2528, '/Users/badapple330/Desktop/pythoncode/humansizeのコピー.py'), (2528, '/Users/badapple330/Desktop/pythoncode/humansizeのコピー3.py'), (2528, '/Users/badapple330/Desktop/pythoncode/humansizeのコピー2.py')]
>>> import humansize
>>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob("*.py")] ③
[('2.5 KiB', 'humansizeのコピー5.py'), ('2.5 KiB', 'humansizeのコピー4.py'), ('2.5 KiB', 'humansize.py'), ('2.5 KiB', 'humansizeのコピー.py'), ('2.5 KiB', 'humansizeのコピー3.py'), ('2.5 KiB', 'humansizeのコピー2.py')]
- importは複数のモジュールを1回でimportできる
- 作業ディレクトリ内部の.pyファイルすべてを探し、ファイルサイズ
os.stat(f).st_size
と絶対パスos.path.realpath(f)
からなるタプルを返す。 - それぞれの.pyファイルのファイルサイズと名前をタプルで返している。
・()はタプルってつい忘れちゃいそうになる
・リスト内包表記のif節は後ろ。一時変数は前
3.4 辞書内包表記
辞書内包表記は、リストの代わりに辞書を構築する。
>>> import os, glob
>>> metadata = [(f, os.stat(f)) for f in glob.glob("*size*.py")] ①
>>> metadata[0] ②
('humansizeのコピー5.py', os.stat_result(st_mode=33188, st_ino=8593213188, st_dev=16777220, st_nlink=1, st_uid=501, st_gid=20, st_size=2528, st_atime=1518757665, st_mtime=1518243422, st_ctime=1518757664))
>>> metadata_dict = {f:os.stat(f) for f in glob.glob("*size*.py")} ③
>>> type(metadata_dict) ④
<class 'dict'>
>>> list(metadata_dict.keys()) ⑤
['humansizeのコピー.py', 'humansizeのコピー2.py', 'humansizeのコピー4.py', 'humansize.py', 'humansizeのコピー3.py', 'humansizeのコピー5.py']
>>> metadata_dict["humansize.py"].st_size ⑥
2528
1. これは辞書内包表記ではなく、リスト内包表記。ファイル名にsizeを含む.pyファイルを探し。(ファイル名, ファイルのメタデータos.stat()
で取得)のタプルを構築
2. 返されたリストの各要素はタプル
3. 辞書内包表記。違い1、[]ではなく{}の波括弧で囲われている。
違い2、コロンでわけられたkeyとvalueで記述されていること。コロン前のfがkey。コロン後のos.stat(f)のメタデータが値だ。
4. type()
で型を調べれば3.は辞書だ。
5. 辞書のキーは作業ディレクトリの"size.py"群だ。
6. 辞書の各キーに対する戻り値は、os.stat()
による各ファイルのメタデータだ。ディレクトリ内部のファイル名を渡せば、メタデータを取得できる辞書が5.ってことだ。humansize.pyは2528byteで丸裸。
リスト内包表記と同様に、辞書内包表記の中にif節を含めることができる。各要素ごとに評価し、入力されたkeyや値を選別できる。
>>> metadata_dict = {f:os.stat(f) for f in glob.glob("*")} ①
>>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \
... for f, meta in metadata_dict.items() if meta.st_size > 2500} ②
>>> list(humansize_dict.keys()) ③
['humansizeのコピー2', 'humansizeのコピー4', 'humansizeのコピー5', 'humansizeのコピー', 'humansize', 'humansizeのコピー3']
>>> humansize_dict{"humansize"}
File "<stdin>", line 1
humansize_dict{"humansize"}
^
SyntaxError: invalid syntax
>>> humansize_dict["humansize"] ④
'2.5 KiB'
- この辞書内包表記は、作業ディレクトリのすべてのファイル名を検索し、それぞれを辞書のキーとする。キーに対応する値は、
os.stat(ファイル名)
のそれぞれのメタデータだ。 - if節が追加された辞書内包表記。ファイルサイズが2500byte以上のものをフィルタで除外している。そうやって選別されたリストをもとに、ファイル名から
os.path.splitext()[0]
で拡張子を取り除きいたものをキー。(splitext()
はファイル名と拡張子に分ける関数)。ファイルのだいたいの大きさをキーに対する値として登録している。 - 辞書のキーは6つある
- 普通の辞書のように、辞書名[キー名]でキーに対応する値が戻る。{キー名}ではエラー。
3.4.1 辞書内包表記に関する他の愉快なもの
いつか役に立つかもしれない辞書内包表記の小ネタ。
キーと値を交換する。
>>> a_dict = {"a":1, "b":2, "c":3}
>>> a_dict
{'c': 3, 'a': 1, 'b': 2}
>>> {value:key for key, value in a_dict.items()}
{1: 'a', 2: 'b', 3: 'c'}
- このkey, valueの交換は文字列やタプルのようにイミュータブル(immutable)であるときのみ動作する。リストを値に含んだ辞書で行うと、失敗する。
>>> {value:key for key, value in a_dict.items()}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <dictcomp>
TypeError: unhashable type: 'list'
>>> a_dict.keys() ①
dict_keys(['c', 'a', 'b'])
>>> a_dict.values() ②
dict_values([3, 1, 2])
>>> a_dict.items() ③
dict_items([('c', 3), ('a', 1), ('b', 2)])
>>> for key, value in a_dict.items(): ④
... print(key)
... print(value)
...
c
3
a
1
b
2
-
dict.keys()
辞書内のすべてのキーを含むのリストを取得 -
dict.values()
辞書ないすべての値を含むリストを取得 -
dict.items()
辞書内に含まれる各要素(key, value)のタプル型をオブジェクトを作成し、リストの内部に入れて返す。 -
for key, value in a_dict.items():
このfor節において、一時変数を2つ用意し , で区切ればば1回のloopでkey, valueの2つを取得できる。dict.items()
はタプルを要素に持つリスト。タプルの中のkeyが一時変数その1に渡され、valueが一時変数その2に渡される。
3.5 集合内包表記
集合も独自の内包表記を持っている。辞書内包表記にとても良くにている。唯一の違いはキーと値のペアの代わりに、値だけしか持たないこと。
>>> a_set = set(range(10))
>>> a_set
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> {x ** 2 for x in a_set} ①
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
>>> {x for x in a_set if x % 2 == 0} ②
{0, 8, 2, 4, 6}
>>> {2**x for x in range(10)} ③
{32, 1, 2, 64, 4, 128, 256, 512, 8, 16}
・集合も{}でつくる。から集合は、 変数名 = set()
でつくる。
変数名 = {}
だとからの辞書ができる。
- 集合内包表記は、入力して集合を受け取けとれる。これは0-9の2乗を求めている。
- if節でフィルタリングできる。
- 入力が集合じゃなくても、シーケンスでも入力として受け取れる。集合は懐が深い。
3.6 もっと知りたい場合は
もっと知りたい人のために#
osモジュール
os — Portable access to operating system specific features
os.pathモジュール
os.path — Platform-independent manipulation of file names
globモジュール
glob — Filename pattern matching
timeモジュール
time — Functions for manipulating clock time
List comprehensions
Nested list comprehensions
Looping techniques
http://diveintopython3-ja.rdy.jp/comprehensions.html
終わりに
最近日が伸びた。5時半で明るい。けどねっむい。