今さらPython3 (25) - 内包表記(Comprehensions)

引き続き第4章です。

入門 Python 3

入門 Python 3

Comprehensionsを日本語表記すると内包表記となるんですね。

リストの内包表記

1~5までの整数が入ったリストを作りましょう。

>>> number_list = []
>>> number_list.append(1)
>>> number_list.append(2)
>>> number_list.append(3)
>>> number_list.append(4)
>>> number_list.append(5)
>>> number_list
[1, 2, 3, 4, 5]
>>>
>>> number_list = []
>>> for number in range(1,6):
...    number_list.append(number)
...
>>> number_list
[1, 2, 3, 4, 5]
>>> number_list = list(range(1,6))
>>> number_list
[1, 2, 3, 4, 5]
>>>

どの方法でもできるけど、more Pythonic wayは内包表記を使う事ですとのこと。

>>> number_list = [number for number in range(1,6)]
>>> number_list
[1, 2, 3, 4, 5]
>>> number_list = [number-1 for number in range(1,6)]
>>> number_list
[0, 1, 2, 3, 4]
>>>

あ、これまで何回か使ってきたやつね。やっとアタマの中でつながったよ。

>>> a_list = [number for number in range(1,6) if number % 2 == 1]
>>> a_list
[1, 3, 5]

内包表記の中で条件も入れられるから、下みたいなソースを書かなくても良いよねということ。

>>> a_list = []
...     if number %2 == 1:
...         a_list.append(number)
...
>>> a_list
[1, 3, 5]

今度は入れ子のケースも内包表記でイケるという話。

>>> rows = range(1,4)
>>> cols = range(1,3)
>>> for row in rows:
...    for col in cols:
...        print(row,col)
...
1 1
1 2
2 1
2 2
3 1
3 2
>>>
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> cells
[(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)]
>>> for cell in cells:
...   print(cell)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
>>> for row, col in cells:
...     print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2
>>>

念のため確認で、行列の組み合わせをタプル(tuple)にしない場合を試してみた。

>>> cells = [row, col for row in rows for col in cols]
  File "<stdin>", line 1
    cells = [row, col for row in rows for col in cols]
                        ^
SyntaxError: invalid syntax
>>> cells = [(row, col) for row in rows for col in cols]
>>> cells
[(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)]
>>>

怒られるのね。

辞書、セットの内包表記

リストがイケるなら辞書もという話。

>>> word = 'letters'
>>> letter_counts = {letter: word.count(letter) for letter in word}
>>> letter_counts
{'e': 2, 't': 2, 'l': 1, 'r': 1, 's': 1}
>>> letter_counts = {letter: word.count(letter) for letter in set(word)}
>>> letter_counts
{'s': 1, 't': 2, 'e': 2, 'l': 1, 'r': 1}
>>>

各文字をキーにして、文字の使用回数を値として辞書にしている訳ですね。最初の方が同じ文字を2回カウントしているのは分かるけど、setにした方が速いのかは要確認かも。setの生成で時間が掛かるかもしれないし。ま、文字の重複度との兼ね合いだな。

>>> a_set = {number for number in range(1,6) if number %3 == 1}
>>> a_set
{1, 4}

1~5の範囲で3で割ると1余る整数のセットができますと。

でもタプルの内包表記はない

>>> number_thing = (number for number in range(1,6))
>>> number_thing
<generator object <genexpr> at 0x007D1C10>
>>> type(number_thing)
<class 'generator'>
>>> number_thing[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
>>> for number in number_thing:
...    print(number)
...
1
2
3
4
5
>>> number_list = list(number_thing)
>>> number_list
[]
>>> list(number_thing)
[]
>>> for number in number_thing:
...   print(number)
...
>>>

ん?なんか本と動きが違うぞ。もう1回。

>>> number_thing = (number for number in range(1,6))
>>> for number in number_thing:
...   print(number)
...
1
2
3
4
5
>>> number_list = list(number_thing)
>>> number_list
[]
>>> number_thing
<generator object <genexpr> at 0x007D1CB0>
>>> for number in number_thing:
...    print(number)
...
>>>

変だと思ったら、ちゃんと説明が書いてありますね。generatorは1回しか使えないので、後から見ようとしても忘れられていると。なので、こんな感じでlistで渡して再利用するしかないみたい。

>>> number_thing = (number for number in range(1,6))
>>> number_list = list(number_thing)
>>> number_list
[1, 2, 3, 4, 5]
>>> for number in number_list:
...    print(number)
...
1
2
3
4
5
>>>

じゃ、タプルを作りたければ、こうすればいいんだよね?

>>> number_thing = (number for number in range(1,6))
>>> number_tuple = tuple(number_thing)
>>> type(number_tuple)
<class 'tuple'>
>>> number_tuple
(1, 2, 3, 4, 5)
>>>

(つづく)