内包表記

飛び込む

ファイルシステムの操作を支援する2つのモジュールについて。

ファイルとディレクトリを扱う

Python 3には os というモジュールが付属している。 os は「オペレーティングシステム」の略。

osモジュールには、ローカルディレクトリ・ファイル・プロセス・環境変数の情報を取得(ときには操作)するための関数が山ほど入っている。Pythonは、サポートするオペレーティングシステムのどれに対しても、統一されたapiを提供できるように最善を尽くしているので、プログラムは、どのコンピュータ上でも最小限の環境依存コードだけで実行することができる。

現在の作業ディレクトリ

osモジュールには、現在の作業ディレクトリを扱うための2つの関数が入っている。

osモジュールはPythonに付属しているので、いつでもどこでもこれをインポートできる。

In [1]:
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つ以上のパス名の断片をもとにパス名を組み立てる。 この例では、文字列を単純に連結している。

In [2]:
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() を使っていれば、混在しても問題ない。

In [3]:
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モジュールには、フルパス名・ディレクトリ名・ファイル名を構成要素に分解するための関数も入っている。

In [4]:
pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py'

split関数は、フルパス名を分解して、パスとファイル名を含むタプルを返す。

In [5]:
os.path.split(pathname)
Out[5]:
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')

split関数の戻り値を、 2つの変数からなるタプルへ代入している。 各々の変数は、対応する要素の値を戻り値のタプルから受け取る

1つ目の変数dirnameは、 os.path.split()関数 から返されたタプルの1つ目の要素(ファイルパス)を受けとる。

In [6]:
(dirname, filename) = os.path.split(pathname)
dirname
Out[6]:
'/Users/pilgrim/diveintopython3/examples'

2つ目の変数filenameは、os.path.split()関数 から返されたタプルの2つ目の要素(ファイル名)を受けとる。

In [7]:
filename
Out[7]:
'humansize.py'

os.path には、os.path.splitext() という関数もある。この関数はファイル名を分解して、 ファイル名と拡張子からなるタプルを返す。 ここでも、同じテクニックを使って各要素を別々の変数に代入している。

In [8]:
(shortname, extension) = os.path.splitext(filename)
shortname
Out[8]:
'humansize'
In [9]:
extension
Out[9]:
'.py'

ディレクトリの内容を見る

globモジュール は、Python標準ライブラリに含まれるツールの1つで、 ディレクトリの内容をプログラムから簡単に取得する方法を提供してくれる。 このモジュールではワイルドカードの一種を使用する。

lsコマンドみたいなもの。

In [10]:
import glob
glob
Out[10]:
<module 'glob' from 'C:\\Miniconda3\\lib\\glob.py'>

globモジュールは、ワイルドカードを受け取って、そのワイルドカードにマッチするすべてのファイルとディレクトリのパスを返す。 この例では、ワイルドカードはディレクトリパスに "*.ipynb" を加えたもので、 カレントディレクトリにあるすべての.ipynbファイルにマッチする。

In [11]:
glob.glob('*.ipynb')
Out[11]:
['DiveIntoPython3_01.ipynb',
 'DiveIntoPython3_02.ipynb',
 'DiveIntoPython3_03.ipynb']

globパターンの中でワイルドカードを複数回使うこともできる。 この例では、現在の作業ディレクトリの中から、.pyという拡張子で終わり、 testという単語をファイル名のどこかに含むファイルを見つける。

In [12]:
 glob.glob('*test*.py')
Out[12]:
[]

ファイルのメタデータを取得する

たいていのファイルシステムは、 各ファイル毎にメタデータ — 作成日・最終更新日・ファイルサイズなど — を格納している。

Pythonは、これらのメタデータにアクセスするための単一のapiを用意している。 メタデータにアクセスするためにファイルを開く必要はなく、ファイル名だけがあればいい。

os.stat()関数を呼び出すと、そのファイルに関する何種類かのメタデータを含んだオブジェクトが返される。

In [13]:
metadata = os.stat('DiveIntoPython3_01.ipynb') 
metadata
Out[13]:
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日として定義されている)からの経過秒数。

In [14]:
metadata.st_mtime
Out[14]:
1459270020.405709

timeモジュールはPython標準ライブラリに含まれている。 このモジュールには、異なる時間表現に変換するための関数や、時刻の値を文字列にする関数、タイムゾーンをいじる関数などが入っている。

In [15]:
import time
time
Out[15]:
<module 'time' (built-in)>

time.localtime()関数は、(os.stat()関数の戻り値のst_mtimeプロパティから取得した) エポック時からの経過秒数の時刻値を、年・月・日・時・分・秒などで構成されるもっと便利な構造へと変換する。

このファイルの最終更新時刻は、2016年3月30日の午前1時47分くらい。

In [16]:
time.localtime(metadata.st_mtime)
Out[16]:
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バイト。

In [17]:
metadata.st_size
Out[17]:
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 を適用し、その結果を戻り値のリストに追加する。

In [18]:
a_list = [1, 9, 8, 4]
[elem * 2 for elem in a_list]
Out[18]:
[2, 18, 16, 8]

リスト内包表記は新しいリストを作るので、元のリストには影響を与えない。

In [19]:
a_list
Out[19]:
[1, 9, 8, 4]
In [20]:
a_list = [elem * 2 for elem in a_list]
a_list
Out[20]:
[2, 18, 16, 8]

リスト内包表記の中ではPythonのすべての式が使える。もちろん、ファイルやディレクトリを操作するためのosモジュールの関数もその式として使える。

In [21]:
import os, glob
glob.glob('*.ipynb')
Out[21]:
['DiveIntoPython3_01.ipynb',
 'DiveIntoPython3_02.ipynb',
 'DiveIntoPython3_03.ipynb']
In [22]:
[os.path.realpath(f) for f in glob.glob('*.ipynb')]
Out[22]:
['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と評価された場合に出力に加えられる。この

In [23]:
import os, glob
[f for f in glob.glob('*.ipynb') if os.stat(f).st_size > 40000]
Out[23]:
['DiveIntoPython3_01.ipynb', 'DiveIntoPython3_02.ipynb']

リスト内包表記はいくらでも複雑にできる。

このリスト内包表記は現在の作業ディレクトリにあるすべての.ipynbファイルを探しだし、 そのファイルサイズを(os.stat()関数を呼びだして)取得し、

  • ファイルサイズ
  • ファイル名(拡張子なし)

からなるタプルを返す。

In [24]:
import os, glob
[(os.stat(f).st_size, os.path.splitext(f)[0]) for f in glob.glob('*.ipynb')] 
Out[24]:
[(40139, 'DiveIntoPython3_01'),
 (123150, 'DiveIntoPython3_02'),
 (29605, 'DiveIntoPython3_03')]

辞書内包表記

辞書内包表記はリスト内包表記に似ているが、こちらはリストの代わりに辞書を構築する。

In [25]:
import os, glob
metadata = [(f, os.stat(f)) for f in glob.glob('*.ipynb')]
metadata[0]  
Out[25]:
('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つの違いがある。

  • 1つ目の違いは、角括弧の代わりに波括弧で包まれている
  • 2つ目の違いは、1つの式ではなく、コロンで分けられた2つの式が入っていることだ。 コロンの前にあるもの(この例ではf)が辞書のキーであり、 コロンの後ろにあるもの(この例ではos.stat(f))が辞書の値である。

辞書内包表記は辞書を返す。

In [26]:
metadata_dict = {f: os.stat(f) for f in glob.glob('*.ipynb')}
type(metadata_dict)
Out[26]:
dict

辞書のキーは、glob.glob('*.ipynb')関数の呼び出しで返されたファイル名になっている。

In [27]:
metadata_dict.keys()
Out[27]:
dict_keys(['DiveIntoPython3_03.ipynb', 'DiveIntoPython3_02.ipynb', 'DiveIntoPython3_01.ipynb'])

各キーに関連づけられた値はos.stat()関数の戻り値だ。 だから、この辞書にディレクトリ内にあるファイル名を渡せば、 そのファイルのメタデータを取得できることになる。 メタデータの1つはファイルサイズを表す st_size 。

In [28]:
metadata_dict['DiveIntoPython3_02.ipynb'].st_size
Out[28]:
123150

リスト内包表記と同様に、辞書内包表記の中にif節を含めることができ、各要素ごとに評価される式に基づいて入力シーケンスをフィルタリングできる。

この辞書内包表記は、現在の作業ディレクトリのすべてのファイルのリストを作り(glob.glob('*'))、 各ファイルのメタデータを取得し (os.stat(f))、キーが各ファイルのファイル名で、値が各ファイルのメタデータになっている辞書を構築する。

この辞書内包表記は、先の内包表記をもとにしている。ファイルサイズが6000バイトよりも小さい (if meta.st_size > 6000)ものをフィルタで除外し、 そのフィルタリング済みのリストをもとに、 ファイル名から拡張子を取り除いたもの(os.path.splitext(f)[0])をキーとして、 ファイルの大きさ(meta.st_size)を値とした辞書を構築する。

In [29]:
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()
Out[29]:
dict_keys(['DiveIntoPython3_02', 'DiveIntoPython3_01', 'DiveIntoPython3_03'])
In [30]:
size_dict['DiveIntoPython3_01']
Out[30]:
40139

辞書内包表記に関する他の愉快なもの

辞書内包表記を使った小技。 キーと値を交換する。

In [31]:
 a_dict = {'a': 1, 'b': 2, 'c': 3}
{value: key for key, value in a_dict.items()}
Out[31]:
{1: 'a', 2: 'b', 3: 'c'}

これは辞書の値が文字列やタプルのようにイミュータブルであるときにだけ動作する。 例えば、リストを含んだ辞書でこれを行おうとすると、見事に失敗する。

In [32]:
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'

集合内包表記

集合も独自の内包表記を持っている。辞書内包表記にとてもよく似ていて、唯一の違いは、キーと値のペアの代わりに値だけを持つ。

In [33]:
a_set = set(range(10))
a_set
Out[33]:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

集合内包表記は入力として集合を受け取ることができる。 この集合内包表記では、0から9までの数の集合の2乗を求めている。

In [34]:
{x ** 2 for x in a_set}
Out[34]:
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

集合内包表記にも、 要素をフィルタリングするためのif節を付けることができる。 以下は0から9までで偶数のみの集合。

In [35]:
{x for x in a_set if x % 2 == 0}
Out[35]:
{0, 2, 4, 6, 8}

集合内包表記の入力が集合である必要はない。 どんなシーケンスでも入力として受けとることができる。

In [36]:
{2 ** x for x in range(10)}
Out[36]:
{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}

参考リンク