通常のリスト生成

(リスト外延表記: list extension)

In [1]:
extension_1 = []
for i in range(10):
    extension_1.append(i)
extension_1
Out[1]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

リスト内包表記

list comprehension

基本構文
[ element for element in iterator]
僕は先に[i for i in]だけ書いてから修飾することが多いです。

In [2]:
comprehension_1= [i for i in range(10)]
comprehension_1
Out[2]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

速度

In [3]:
%%timeit
extension_1 = []
for i in range(10000):
    extension_1.append(i)
100 loops, best of 3: 3.37 ms per loop
In [4]:
%%timeit
comprehension_1= [i for i in range(10000)]
1000 loops, best of 3: 1.05 ms per loop

list内包表記はコードがすっきりするだけでなく速度面でも有利です
参考: Pythonの内包表記はなぜ速い?
遅い理由は大きく2つ

  • ループする度にリストオブジェクトのappendを参照する
  • appendをpythonの関数として実行する

前者の影響はループの外に参照を追い出すことでも解消できます

In [5]:
%%timeit
extension_1_ = []
append=extension_1_.append
for i in range(10000):
    append(i)
1000 loops, best of 3: 1.57 ms per loop

ifを含む場合(後置if)

リスト内包表記は後置ifが使えます

In [6]:
extension_2 =[]
for i in range(10):
    if i%2==0:
        extension_2.append(i)
extension_2
Out[6]:
[0, 2, 4, 6, 8]
In [7]:
comprehension_2 = [i for i in range(10) if i%2==0]
comprehension_2
Out[7]:
[0, 2, 4, 6, 8]
In [8]:
%%timeit
extension_2 =[]
for i in range(10000):
    if i%2==0:
        extension_2.append(i)
100 loops, best of 3: 4.08 ms per loop
In [9]:
%%timeit
comprehension_2 = [i for i in range(10000) if i%2==0]
100 loops, best of 3: 3.15 ms per loop
In [10]:
%%timeit
extension_2_ =[]
append=extension_2_.append
for i in range(10000):
    if i%2==0:
        append(i)
100 loops, best of 3: 3.81 ms per loop

実はifが計算律速になるので、無理にリスト内包表記にする必要がなかったりします。

if ~ elseを含む場合 (条件演算子)

紛らわしいですが、else節を含む場合は条件演算子(他で言う三項演算子)を使うのでifの位置が変わります
(条件演算子はpython 2.5以降のみ対応です)

In [11]:
extension_3 =[]
for i in range(10):
    if i%2==0:
        extension_3.append(i)
    else:
        extension_3.append(str(i))
extension_3
Out[11]:
[0, '1', 2, '3', 4, '5', 6, '7', 8, '9']
In [12]:
comprehension_3 = [ i if i%2==0 else str(i) for i in range(10)]
comprehension_3
Out[12]:
[0, '1', 2, '3', 4, '5', 6, '7', 8, '9']

実際にはこちらと等価だと思えば理解しやすいかもしれません

In [13]:
extension_3_conditional =[]
for i in range(10):
    extension_3_conditional.append(i) if i%2==0 else extension_3_conditional.append(str(i))
extension_3_conditional
Out[13]:
[0, '1', 2, '3', 4, '5', 6, '7', 8, '9']

一応速度を測ってみます

In [14]:
%%timeit
extension_3 =[]
for i in range(10000):
    if i%2==0:
        extension_3.append(i)
    else:
        extension_3.append(str(i))
100 loops, best of 3: 12.4 ms per loop
In [15]:
%%timeit
extension_3_ =[]
append=extension_3_.append
for i in range(10000):
    if i%2==0:
        append(i)
    else:
        append(str(i))
100 loops, best of 3: 10.3 ms per loop
In [16]:
%%timeit
extension_3_conditional_ =[]
append=extension_3_conditional_.append
for i in range(10000):
    append(i) if i%2==0 else append(str(i))
100 loops, best of 3: 9.04 ms per loop
In [17]:
%%timeit
comprehension_3 = [ i if i%2==0 else str(i) for i in range(10000)]
100 loops, best of 3: 8.6 ms per loop

あまり内包表記による高速化はありません

辞書内包表記とセット内包表記

python2.7以降ではリスト以外の内包表記として、辞書内包やセット内包も使えます。

In [18]:
comprehension_dict = {str(i):i for i in range(10)}
print(comprehension_dict)
{'7': 7, '8': 8, '2': 2, '9': 9, '0': 0, '1': 1, '6': 6, '5': 5, '4': 4, '3': 3}

zipとかと相性いいです。

In [19]:
label = ["kinoko", "takenoko", "suginoko"]
feature = ["yama", "sato", "mura"]
{i:j for i,j in zip(label,feature)}
Out[19]:
{'kinoko': 'yama', 'suginoko': 'mura', 'takenoko': 'sato'}

python2.6まではdictにtupleを渡してあげます。

In [20]:
comprehension_dict2 = dict((str(i),i) for i in range(10))
print(comprehension_dict2)
{'7': 7, '8': 8, '2': 2, '9': 9, '0': 0, '1': 1, '6': 6, '5': 5, '4': 4, '3': 3}

後置ifも使えます。

In [21]:
comprehension_dict2 = {str(i):i for i in range(10) if i%2==0}
print(comprehension_dict2)
{'8': 8, '6': 6, '2': 2, '4': 4, '0': 0}

条件演算子も使えます。
条件演算子なので、「:」の前後のkeyとvalueそれぞれに記載する必要があります。

In [22]:
comprehension_dict3 = {str(i) if i%2==0 else i : i if i%2==0 else str(i) for i in range(10)}
print(comprehension_dict3)
{'2': 2, 1: '1', 3: '3', 5: '5', '0': 0, 7: '7', 9: '9', '6': 6, '4': 4, '8': 8}

これは動かないです。(以前やりましたorz)

In [23]:
comprehension_dict4 = {str(i):i if i%2==0 else i:str(i) for i in range(10)}
  File "<ipython-input-23-60c72c76aa53>", line 1
    comprehension_dict4 = {str(i):i if i%2==0 else i:str(i) for i in range(10)}
                                                    ^
SyntaxError: invalid syntax

セット内包表記にしたい時は : なしで {} で囲みます。

In [24]:
comprehension_set={i%5 for i in range(10)}
comprehension_set
Out[24]:
{0, 1, 2, 3, 4}

ジェネレータ式とタプル内包表記

構文から勘違いしやすいですが、()で囲んでもタプル内包表記になりません。

In [25]:
comprehension_gen=(i%5 for i in range(10))
comprehension_gen
Out[25]:
<generator object <genexpr> at 0x7f3000219678>
In [27]:
for i in comprehension_gen:print(i)
0
1
2
3
4
0
1
2
3
4

()で囲うとジェネレータが発生します。むしろタプル内包表記より使います。
リストと違ってメモリ中に全要素を格納しないで、次の要素を順番に生成します。
内包表記を使わない書き方は下記ですが、一旦 ジェネレータを生成する関数を作らないと行けないので面倒です。

In [28]:
def gen_func():
    for i in range(10):
        yield i
extension_gen = gen_func()
extension_gen
Out[28]:
<generator object gen_func at 0x7f3000166830>

タプル内包表記が必要なことはあまりないですが、もしどうしても必要ならリスト内包表記をtuple関数に渡すしかないです。

In [29]:
tuple([i for i in range(10)])
Out[29]:
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

闇への道

この節を読み終えた後はリーダブルコードを読んで浄化されることを推奨します。

条件演算子を複数つなげる

In [30]:
for i in range(1,16):
    if i%15==0:
        print ("fizzbuzz")
    elif i%3==0:
        print ("fizz")
    elif i%5==0:
        print ("buzz")
    else:
        print(i)
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
In [31]:
["fizzbuzz" if i%15==0 else "fizz" if i%3==0 else "buzz"
 if i%5==0 else i for i in range(1,16)]
Out[31]:
[1,
 2,
 'fizz',
 4,
 'buzz',
 'fizz',
 7,
 8,
 'fizz',
 'buzz',
 11,
 'fizz',
 13,
 14,
 'fizzbuzz']

ネスト(多重配列)

In [32]:
outer_list=[]
for i in range(3):
    innter_list=[]
    for j in range(10):
        innter_list.append(j)
    outer_list.append(innter_list)
outer_list
Out[32]:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
In [33]:
[[j for j in range(10)] for i in range(3)]
Out[33]:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]

フラッタン
この辺から可読性著しく悪くなります。

In [34]:
init=[[1,2,3],[4,5],[6,7]]
In [35]:
flatten=[]
for outer in init:
    for inner in outer:
        flatten.append(inner)
flatten
Out[35]:
[1, 2, 3, 4, 5, 6, 7]

これと等価

In [36]:
[inner for outer in init for inner in outer]
Out[36]:
[1, 2, 3, 4, 5, 6, 7]

基本的には左側のfor節から順番に読んでいって、最後にリストに入れるものが頭にくる
インデントするならこんな感じ

In [37]:
[
inner
for outer in init
    for inner in outer
]
Out[37]:
[1, 2, 3, 4, 5, 6, 7]

パタトクカシー

In [38]:
pato="パトカー"
taxi="タクシー"
rslt=[]
for i in zip(pato,taxi):
    for j in i:
        rslt.append("".join(j))
"".join(rslt)
Out[38]:
'パタトクカシーー'
In [39]:
"".join(["".join(j) for i in zip(pato,taxi) for j in i])
Out[39]:
'パタトクカシーー'

インデントすると

In [40]:
"".join(
  ["".join(j)
    for i in zip(pato,taxi)
      for j in i
  ]
)
Out[40]:
'パタトクカシーー'

printとか入れてもできる

In [41]:
for i in zip(pato,taxi):
    for j in i:
        for k in j: print(k)
パ
タ
ト
ク
カ
シ
ー
ー
In [42]:
[print(k) for i in zip(pato,taxi) for j in i for k in j]
パ
タ
ト
ク
カ
シ
ー
ー
Out[42]:
[None, None, None, None, None, None, None, None]

if 節との多重ループの合わせ技

In [43]:
import re
DIO=["U","無駄","RR","貧弱ゥ","Y"]
In [44]:
rslt=[]
for i in DIO:
    if re.match("[URY]+",i):
        for j in i:
            rslt.append(j*2)
"".join(rslt)
Out[44]:
'UURRRRYY'
In [45]:
"".join([j*3 for i in DIO if re.match("[URY]+",i) for j in i])
Out[45]:
'UUURRRRRRYYY'

インデントするとこんな感じ

In [46]:
"".join(
  [
    j*4
      for i in DIO
        if re.match("[URY]+",i)
          for j in i
    ]
)
Out[46]:
'UUUURRRRRRRRYYYY'