
Python チュートリアルを読んで気になったところをメモ。
データ構造1
squares = list(map(lambda x: x**2, range(10))) or squares = [x**2 for x in range(10)] or # 冗長な例 squares = [(lambda x: x**2)(x) for x in range(10)]
処理の流れ1:
lambda x: x**2を評価、無名関数のオブジェクトを作るrange(10)を評価、range 0-9 を作る(要素は未生成)map(<lambda>, <range>)を評価、map iterator を作る(まだ未計算)list(<map-iterator>)を評価、反復を開始- map が range 要素から次の値 x を1つずつ取り出す
- 取り出した
xに無名関数を適用 =x**2を計算 - 結果の要素を
[]の list object に変換
- range が終わると反復終了
処理の流れ2:
squares = [x**2 for x in range(10)]
- range(10) を評価、range 0-9 を作る(要素は未生成)
- [] の内包表記で空の list を作成して range 要素から次の値 x を1つずつ取り出す
- for 反復ステップ
- x を1つずつ(0~9)まで取り出す
- x**2 を計算し、結果を list に追加
- x を処理し終えると、出来上がった list がそのまま完成
list() と [] の書き方は、簡易的な書き方になっているだけではない。list() は map を評価する働きがあるが [] はそのまま格納する。
なので、以下の書き方だと [] は map をそのまま格納して、処理は終わり。
squares = [map(lambda x: x**2, range(10))]
[<map object at 0x7fd6fb1c3130>]
データ構造2
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] or combs = [] for x in [1,2,3]: for y in [3,1,4]: if x != y: combs.append((x, y))
普段は、1行でいくつもの処理を(意図してたくさん)まとめた書き方をしないので、処理の流れを追いづらかった。
個人的には、わかりやすい下の書き方を選択しがち。シンプルな内容なら内包表記も悪くないと思う。
処理の流れ:
- 外側のループ
- 最初に
for x in [1,2,3]が動く xは順番に 1,2,3- 内側のループ
- 各
xに対してfor y in [3,1,4]が動く yは順番に 3,1,4- if 条件のチェック
(x, y)リストに追加する前にif x != yを評価- True ならリストに入れる
<式> の「最終的にリストに追加する要素」を表す部分が最初にくる。そのあと for で値を処理する。内包表記は、最初に最終的なデータ型のゴールが示されている点は留意したほうがよい。
[ <式> for 変数 in イテラブル (if 条件 …) ]
データ構造3
matrix = [ [1,2,3,4], [5,6,7,8], [9,10,11,12], ] [[row[i] for row in matrix] for i in range(4)]
<式> にあたるのは [第2処理] なので、とりあえず配列を得ることがわかる。
そのほかのところは単純で for i in range(4) なので i = 0~3 の繰り返しをするだけ。あとは [第2処理] の中がどうなっているのかを考える。
処理 i = 0:
- 第2処理のループで row[i] = row[0] を集める:
- row = [1,2,3,4] → row[0] = 1
- row = [5,6,7,8] → row[0] = 5
- row = [9,10,11,12] → row[0] = 9
[]はそのままリスト化するので [1,5,9] になる
あとも流れは同じ:
- [1,5,9]
- [2,6,10]
- [3,7,11]
- [4,8,12]
それぞれの list を外側の list に追加する。
tuple
私の tuple の理解に、すこし間違っているところがあった。
empty = ()
空の tuple を作るときは上のようにするから、1つの tuple を作るときも下のようにしていた。
single = ('data', ) or single = 'data',
しかし、実際は丸括弧を必要としていない。0個の tuple を作るときが特別に括弧を必要とするのであって、以降は丸括弧を必要としていない。(丸括弧は、処理のグループ化をサポートしているので、全く無意味ではないと思う)
補足:C# ではデータが1個の tuple というのは存在しないはず。0個は作れる。python だと unpacking の都合もあって、一貫した書き方ができたほうが C# よりも都合がよい。
dict
ループのテクニックの整理:
- .key() キーだけ
- .values() 値だけ
- .items() (key, value) の tuple
同じく enumerate() も index を割り当てした iterator を返却する。この時点で tuple のリストに即変換はされていない。中身を見たいときは list() で展開を指示しないとダメ。(通常は iterable なものを受け入れ可能な箇所に対して、そのまま enumerate object は代入できる)
list(enumerate(['tic','tac','toe'])) # => [(0, 'tic'), (1, 'tac'), (2, 'toe')]
内包表記の整理
内包表記は日本語の表記ですが、もともとは comprehension の「含有」の意味です。
- list comprehension []
- 結果は list
- set comprehension {}
- 結果は set
- dictionary comprehension {}
- 結果は dict
- generator expression ()
- ※結果は 遅延評価をする iterator
前の3つは、その時にコレクションをそれぞれ構築してしまうけど generator だけは、遅延評価をする。(なので、大きなデータを作るときは処理的に有利)
gen = (x**2 for x in range(1000000)) next(gen) # 0 next(gen) # 1
set と dict は同じような表記をしてしまう。歴史的には dict が先にあって、あとから set が用意されているはず。
理由として、空データを用意する方法を見る。空集合だけは書き方を区別できないので、先に用意されていた dict の書き方がシンプルになっている。(わずかな部分だけど、一貫性にケチがついているようにも見える)
li = ()
se = set()
di = {}