ファイルシステムの操作を支援する2つのモジュールについて。
Python 3には os
というモジュールが付属している。
os
は「オペレーティングシステム」の略。
os
モジュールには、ローカルディレクトリ・ファイル・プロセス・環境変数の情報を取得(ときには操作)するための関数が山ほど入っている。Pythonは、サポートするオペレーティングシステムのどれに対しても、統一されたapiを提供できるように最善を尽くしているので、プログラムは、どのコンピュータ上でも最小限の環境依存コードだけで実行することができる。
osモジュールには、現在の作業ディレクトリを扱うための2つの関数が入っている。
osモジュールはPythonに付属しているので、いつでもどこでもこれをインポートできる。
import os
現在の作業ディレクトリを知るには、os.getcwd()関数を使えばいい。 jupyter(ipython) の場合、現在の.ipynbファイルがあるディレクトリになる。
print(os.getcwd())
C:\\Users\\user\\workspace
など
現在の作業ディレクトリを変更するには、os.chdir()
関数を使う。
cd
コマンドと似ている。
os.chdir('/Users/')
print(os.getcwd())
C:\\Users
Windows上でも、Linuxスタイルのパス名(普通のスラッシュ、ドライブレター無し)を使用できる。 これは、Pythonがオペレーティングシステムの差異を吸収しようと試みることの結果の1つ。
os.path
には、ファイル名とディレクトリ名を操作するための関数が入っている。
os.path.join()
関数は、1つ以上のパス名の断片をもとにパス名を組み立てる。
この例では、文字列を単純に連結している。
print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py'))
/Users/pilgrim/diveintopython3/examples/humansize.py
pathの最後にスラッシュがなくとも、os.path.join()
がファイル名の前にスラッシュを追加してくれる。
この例はWindows上なので、(フォワード)スラッシュではなくバックスラッシュになっている。
Pythonでは、os.path.join()
を使っていれば、混在しても問題ない。
print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py'))
/Users/pilgrim/diveintopython3/examples\humansize.py
os.path.expanduser()
は、
現在のユーザのホームディレクトリを~(チルダ)で表現しているパスの展開を行う。
これは、Linux, Mac OS X, Windowsといった、ユーザにホームディレクトリが与えられるすべてのプラットフォームで動作する。
print(os.path.expanduser('~'))
C:\Users\user #ホームディレクトリを表示
ユーザのホームディレクトリにあるファイルやディレクトリのパス名を簡単に組み立てられる。
os.path.join()
関数は引数をいくつでも受け取ることができる。
print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py'))
C:\Users\user\diveintopython3\examples\humansize.py
os.path
モジュールには、フルパス名・ディレクトリ名・ファイル名を構成要素に分解するための関数も入っている。
pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py'
split関数
は、フルパス名を分解して、パスとファイル名を含むタプルを返す。
os.path.split(pathname)
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')
split関数
の戻り値を、
2つの変数からなるタプルへ代入している。
各々の変数は、対応する要素の値を戻り値のタプルから受け取る
1つ目の変数dirnameは、
os.path.split()関数
から返されたタプルの1つ目の要素(ファイルパス)を受けとる。
(dirname, filename) = os.path.split(pathname)
dirname
'/Users/pilgrim/diveintopython3/examples'
2つ目の変数filename
は、os.path.split()関数
から返されたタプルの2つ目の要素(ファイル名)を受けとる。
filename
'humansize.py'
os.path
には、os.path.splitext()
という関数もある。この関数はファイル名を分解して、
ファイル名と拡張子からなるタプルを返す。
ここでも、同じテクニックを使って各要素を別々の変数に代入している。
(shortname, extension) = os.path.splitext(filename)
shortname
'humansize'
extension
'.py'
globモジュール
は、Python標準ライブラリに含まれるツールの1つで、
ディレクトリの内容をプログラムから簡単に取得する方法を提供してくれる。
このモジュールではワイルドカードの一種を使用する。
ls
コマンドみたいなもの。
import glob
glob
<module 'glob' from 'C:\\Miniconda3\\lib\\glob.py'>
glob
モジュールは、ワイルドカードを受け取って、そのワイルドカードにマッチするすべてのファイルとディレクトリのパスを返す。
この例では、ワイルドカードはディレクトリパスに "*.ipynb" を加えたもので、
カレントディレクトリにあるすべての.ipynbファイルにマッチする。
glob.glob('*.ipynb')
['DiveIntoPython3_01.ipynb', 'DiveIntoPython3_02.ipynb', 'DiveIntoPython3_03.ipynb']
globパターンの中でワイルドカードを複数回使うこともできる。 この例では、現在の作業ディレクトリの中から、.pyという拡張子で終わり、 testという単語をファイル名のどこかに含むファイルを見つける。
glob.glob('*test*.py')
[]
たいていのファイルシステムは、 各ファイル毎にメタデータ — 作成日・最終更新日・ファイルサイズなど — を格納している。
Pythonは、これらのメタデータにアクセスするための単一のapiを用意している。 メタデータにアクセスするためにファイルを開く必要はなく、ファイル名だけがあればいい。
os.stat()関数を呼び出すと、そのファイルに関する何種類かのメタデータを含んだオブジェクトが返される。
metadata = os.stat('DiveIntoPython3_01.ipynb')
metadata
os.stat_result(st_mode=33206, st_ino=12384898975434789, st_dev=3525838481, st_nlink=1, st_uid=0, st_gid=0, st_size=40139, st_atime=1459234585, st_mtime=1459270020, st_ctime=1459234585)
.st_mtime
は最終更新日、
形式は、エポック時(1970年1月1日として定義されている)からの経過秒数。
metadata.st_mtime
1459270020.405709
time
モジュールはPython標準ライブラリに含まれている。
このモジュールには、異なる時間表現に変換するための関数や、時刻の値を文字列にする関数、タイムゾーンをいじる関数などが入っている。
import time
time
<module 'time' (built-in)>
time.localtime()
関数は、(os.stat()関数の戻り値のst_mtimeプロパティから取得した)
エポック時からの経過秒数の時刻値を、年・月・日・時・分・秒などで構成されるもっと便利な構造へと変換する。
このファイルの最終更新時刻は、2016年3月30日の午前1時47分くらい。
time.localtime(metadata.st_mtime)
time.struct_time(tm_year=2016, tm_mon=3, tm_mday=30, tm_hour=1, tm_min=47, tm_sec=0, tm_wday=2, tm_yday=90, tm_isdst=0)
os.stat()関数は、st_sizeプロパティの中でファイルのサイズも返している。
DiveIntoPython3_01.ipynbファイルのサイズは40139バイト。
metadata.st_size
40139
絶対パス名(つまり、ルートディレクトリもしくはドライブレターに至るまでの、
すべてのディレクトリを含むパス名)を作りたいのであれば、os.path.realpath()
関数で作れる。
print(os.path.realpath('DiveIntoPython3_01.ipynb')) #C:\Users\user\workspace にあったとすると
C:\Users\user\workspace\DiveIntoPython3_01.ipynb
リスト内包表記は、「リストの各要素に関数を適用することでリストを別のリストにマッピング」することができる。
Pythonのインタプリタはa_listの要素を一度に1つずつ取り出していき、 その各々の値を一時変数のelemに代入する。 Python は関数elem * 2 を適用し、その結果を戻り値のリストに追加する。
a_list = [1, 9, 8, 4]
[elem * 2 for elem in a_list]
[2, 18, 16, 8]
リスト内包表記は新しいリストを作るので、元のリストには影響を与えない。
a_list
[1, 9, 8, 4]
a_list = [elem * 2 for elem in a_list]
a_list
[2, 18, 16, 8]
リスト内包表記の中ではPythonのすべての式が使える。もちろん、ファイルやディレクトリを操作するためのosモジュールの関数もその式として使える。
import os, glob
glob.glob('*.ipynb')
['DiveIntoPython3_01.ipynb', 'DiveIntoPython3_02.ipynb', 'DiveIntoPython3_03.ipynb']
[os.path.realpath(f) for f in glob.glob('*.ipynb')]
['C:\\Users\\nabana\\GoogleDrive\\JupyterNote\\DiveIntoPython3_Note\\DiveIntoPython3_01.ipynb', 'C:\\Users\\nabana\\GoogleDrive\\JupyterNote\\DiveIntoPython3_Note\\DiveIntoPython3_02.ipynb', 'C:\\Users\\nabana\\GoogleDrive\\JupyterNote\\DiveIntoPython3_Note\\DiveIntoPython3_03.ipynb']
このリスト内包表記は.ipybファイルのリストを受け取り、 それをフルパス名のリストに変換する。
[os.path.realpath(f) for f in glob.glob('*.ipynb')]
['C:\\Users\\user\\workspace\\DiveIntoPython3_01.ipynb',
'C:\\Users\\user\\workspace\\DiveIntoPython3_02.ipynb',
'C:\\Users\\user\\workspace\\DiveIntoPython3_03.ipynb']
リスト内包表記は、要素のフィルタリングを行うこともでき、 これは元のリストよりも小さなリストを作り出す。
リストをフィルタリングするために、リスト内包表記の最後にif節を付け加えることができる。 ifキーワードの後ろの式はリストの各要素に対して評価され、各要素は式がTrueと評価された場合に出力に加えられる。この
import os, glob
[f for f in glob.glob('*.ipynb') if os.stat(f).st_size > 40000]
['DiveIntoPython3_01.ipynb', 'DiveIntoPython3_02.ipynb']
リスト内包表記はいくらでも複雑にできる。
このリスト内包表記は現在の作業ディレクトリにあるすべての.ipynbファイルを探しだし、 そのファイルサイズを(os.stat()関数を呼びだして)取得し、
からなるタプルを返す。
import os, glob
[(os.stat(f).st_size, os.path.splitext(f)[0]) for f in glob.glob('*.ipynb')]
[(40139, 'DiveIntoPython3_01'), (123150, 'DiveIntoPython3_02'), (29605, 'DiveIntoPython3_03')]
辞書内包表記はリスト内包表記に似ているが、こちらはリストの代わりに辞書を構築する。
import os, glob
metadata = [(f, os.stat(f)) for f in glob.glob('*.ipynb')]
metadata[0]
('DiveIntoPython3_01.ipynb', os.stat_result(st_mode=33206, st_ino=12384898975434789, st_dev=3525838481, st_nlink=1, st_uid=0, st_gid=0, st_size=40139, st_atime=1459234585, st_mtime=1459270020, st_ctime=1459234585))
こっちは、辞書内包表記。 この構文はリスト内包表記に似ているが、2つの違いがある。
コロンの前にあるもの(この例ではf)が辞書のキーであり、 コロンの後ろにあるもの(この例ではos.stat(f))が辞書の値である。
辞書内包表記は辞書を返す。
metadata_dict = {f: os.stat(f) for f in glob.glob('*.ipynb')}
type(metadata_dict)
dict
辞書のキーは、glob.glob('*.ipynb')関数
の呼び出しで返されたファイル名になっている。
metadata_dict.keys()
dict_keys(['DiveIntoPython3_03.ipynb', 'DiveIntoPython3_02.ipynb', 'DiveIntoPython3_01.ipynb'])
各キーに関連づけられた値はos.stat()関数の戻り値だ。 だから、この辞書にディレクトリ内にあるファイル名を渡せば、 そのファイルのメタデータを取得できることになる。 メタデータの1つはファイルサイズを表す st_size 。
metadata_dict['DiveIntoPython3_02.ipynb'].st_size
123150
リスト内包表記と同様に、辞書内包表記の中にif節を含めることができ、各要素ごとに評価される式に基づいて入力シーケンスをフィルタリングできる。
この辞書内包表記は、現在の作業ディレクトリのすべてのファイルのリストを作り(glob.glob('*')
)、
各ファイルのメタデータを取得し (os.stat(f))、キーが各ファイルのファイル名で、値が各ファイルのメタデータになっている辞書を構築する。
この辞書内包表記は、先の内包表記をもとにしている。ファイルサイズが6000バイトよりも小さい
(if meta.st_size > 6000
)ものをフィルタで除外し、
そのフィルタリング済みのリストをもとに、
ファイル名から拡張子を取り除いたもの(os.path.splitext(f)[0]
)をキーとして、
ファイルの大きさ(meta.st_size
)を値とした辞書を構築する。
import os, glob
metadata_dict = {f: os.stat(f) for f in glob.glob('*.ipynb')}
size_dict = {os.path.splitext(f)[0]: meta.st_size
for f, meta in metadata_dict.items() if meta.st_size > 6000}
size_dict.keys()
dict_keys(['DiveIntoPython3_02', 'DiveIntoPython3_01', 'DiveIntoPython3_03'])
size_dict['DiveIntoPython3_01']
40139
辞書内包表記を使った小技。 キーと値を交換する。
a_dict = {'a': 1, 'b': 2, 'c': 3}
{value: key for key, value in a_dict.items()}
{1: 'a', 2: 'b', 3: 'c'}
これは辞書の値が文字列やタプルのようにイミュータブルであるときにだけ動作する。 例えば、リストを含んだ辞書でこれを行おうとすると、見事に失敗する。
a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5}
{value: key for key, value in a_dict.items()}
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-32-78c6fd1613f0> in <module>() 1 a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5} ----> 2 {value: key for key, value in a_dict.items()} <ipython-input-32-78c6fd1613f0> in <dictcomp>(.0) 1 a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5} ----> 2 {value: key for key, value in a_dict.items()} TypeError: unhashable type: 'list'
集合も独自の内包表記を持っている。辞書内包表記にとてもよく似ていて、唯一の違いは、キーと値のペアの代わりに値だけを持つ。
a_set = set(range(10))
a_set
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
集合内包表記は入力として集合を受け取ることができる。 この集合内包表記では、0から9までの数の集合の2乗を求めている。
{x ** 2 for x in a_set}
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
集合内包表記にも、 要素をフィルタリングするためのif節を付けることができる。 以下は0から9までで偶数のみの集合。
{x for x in a_set if x % 2 == 0}
{0, 2, 4, 6, 8}
集合内包表記の入力が集合である必要はない。 どんなシーケンスでも入力として受けとることができる。
{2 ** x for x in range(10)}
{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}