6-1. 内包表記

内包表記について説明します。

参考:

リスト内包表記

Pythonでは各種の内包表記 (comprehension) が利用できます。

以下のような整数の自乗を要素に持つリストを作るプログラムでは、

[1]:
squares1 = []
for x in range(6):
    squares1.append(x**2)
squares1
[1]:
[0, 1, 4, 9, 16, 25]

squares1 として [0, 1, 4, 9, 16, 25] が得られます。 これを内包表記を用いて書き換えると、以下のように一行で書け、プログラムが読みやすくなります。

[2]:
squares2 = [x**2 for x in range(6)]
squares2
[2]:
[0, 1, 4, 9, 16, 25]

関数 sum は与えられた数のリストの総和を求めます。 (2-2の練習にあった sum_list と同じ機能を持つ組み込みの関数です。) 内包表記に対して sum を適用すると以下のようになります。

[3]:
sum([x**2 for x in range(6)])
[3]:
55

以下の内包表記は3-2で用いられていました。

[4]:
[chr(i + ord('a')) for i in range(26)]
[4]:
['a',
 'b',
 'c',
 'd',
 'e',
 'f',
 'g',
 'h',
 'i',
 'j',
 'k',
 'l',
 'm',
 'n',
 'o',
 'p',
 'q',
 'r',
 's',
 't',
 'u',
 'v',
 'w',
 'x',
 'y',
 'z']

練習

文字列のリストが変数 strings に与えられたとき、 それぞれの文字列の長さからなるリストを返す内包表記を記述してください。

strings = ['The', 'quick', 'brown'] のとき、結果は [3, 5, 5] となります。

[5]:
strings = ['The', 'quick', 'brown']
[ここに内包表記を書く]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_6493/3859383579.py in <module>
      1 strings = ['The', 'quick', 'brown']
----> 2 [ここに内包表記を書く]

NameError: name 'ここに内包表記を書く' is not defined

練習

コンマで区切られた10進数からなる文字列が変数 str1 に与えられたとき、 それぞれの10進数を数に変換して得られるリストを返す内包表記を記述してください。

str1 = '123,45,-3' のとき、結果は [123, 45, -3] となります。

なお、コンマで区切られた10進数からなる文字列を、10進数の文字列のリストに変換するには、メソッド split を用いることができます。 また、10進数の文字列を数に変換するには、int を関数として用いることができます。

[6]:
str1 = '123,45,-3'
[ここに内包表記を書く]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_6493/423850202.py in <module>
      1 str1 = '123,45,-3'
----> 2 [ここに内包表記を書く]

NameError: name 'ここに内包表記を書く' is not defined

練習

数のリストが与えらえたとき、リストの要素の分散を求める関数 var を 内包表記と関数 sum を用いて定義してください。 以下のセルの ... のところを書き換えて var を作成してください。

[7]:
def var(lst):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[8]:
print(var([3,4,1,2]) == 1.25)
False

内包表記の入れ子

また内包表記を入れ子ネスト)にすることも可能です:

[9]:
[[x*y for y in range(x+1)] for x in range(4)]
[9]:
[[0], [0, 1], [0, 2, 4], [0, 3, 6, 9]]

ネストした内包表記は、外側から読むとわかりやすいです。 x0 から 3 まで動かしてリストが作られます。 そのリストの要素1つ1つは内包表記によるリストになっていて、 それぞれのリストは y を 0 から x まで動かして得られます。

以下のリストは、上の2重のリストをフラットにしたものです。 この内包表記では、for が2重になっていますが、自然に左から読んでください。 x0 から 3 まで動かし、そのそれぞれに対して y0 から x まで動かします。 その各ステップで得られた x*y の値をリストにします。

[10]:
[x*y for x in range(4) for y in range(x+1)]
[10]:
[0, 0, 1, 0, 2, 4, 0, 3, 6, 9]

以下の関数は、与えられた文字列の全ての空でない部分文字列からなるリストを返します。

[11]:
def allsubstrings(s):
    return [s[i:j] for i in range(len(s)) for j in range(i+1,len(s)+1)]

allsubstrings('abc')
[11]:
['a', 'ab', 'abc', 'b', 'bc', 'c']

練習

次のような関数 sum_lists を作成してください。

  • sum_lists はリスト list1 を引数とします。

  • list1 の各要素はリストであり、そのリストの要素は数です。

  • sum_lists は、list1 の各要素であるリストの総和を求め、それらの総和を足し合せて返します。

ここでは、内包表記と関数 sum を用いて sum_lists を定義してください。 以下のセルの ... のところを書き換えて sum_lists を作成してください。

[12]:
def sum_lists(list1):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[13]:
print(sum_lists([[20, 5], [6, 16, 14, 5], [16, 8, 16, 17, 14], [1], [5, 3, 5, 7]]) == 158)
False

練習

リスト list1list2 が引数として与えられたとき、次のようなリスト list3 を返す関数 sum_matrix を作成してください。

  • list1, list2, list3 は、3つの要素を持ちます。

  • 各要素は大きさ 3 のリストになっており、そのリストの要素は全て数です。

  • list3[i][j] (ただし、ij は共に、0 以上 2 以下の整数)は list1[i][j]list2[i][j] の値の和になっています。

ここでは、内包表記を用いてsum_matrix を定義してください。以下のセルの ... のところを書き換えて sum_matrix を作成してください。

[14]:
def sum_matrix(list1, list2):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[15]:
print(sum_matrix([[1,5,3],[4,5,6],[7,8,9]], [[1,4,7],[2,5,8],[3,6,9]])==[[2, 9, 10], [6, 10, 14], [10, 14, 18]])
False

条件付き内包表記

内包表記は for に加えて if を使うこともできます:

[16]:
words = ['cat', 'dog', 'elephant', None, 'giraffe']
length = [len(w) for w in words if w != None]
print(length)
[3, 3, 8, 7]

この場合、length として要素が None の場合を除いた [3, 3, 8, 7] が得られます。

セット内包表記

内包表記はセット(集合)に対しても使うことができます:

[17]:
words = ['cat', 'dog', 'elephant', 'giraffe']
length_set = {len(w) for w in words}
print(length_set)
{8, 3, 7}

length_set として {3, 7, 8} が得られます。 セット型なので、リストと異なり重複する要素は除かれます。

辞書内包表記

さらに、内包表記は辞書型でも使うことができます。

[18]:
words = ['cat', 'dog', 'elephant', 'giraffe']
length_dic = {w:len(w) for w in words}
print(length_dic)
{'cat': 3, 'dog': 3, 'elephant': 8, 'giraffe': 7}

length_dic として {'cat': 3, 'dog': 3, 'elephant': 8, 'giraffe': 7} が得られます。

長さと文字列を逆にするとどうなるでしょうか。

[19]:
length_rdic = {len(w): w for w in words}
print(length_rdic)
{3: 'dog', 8: 'elephant', 7: 'giraffe'}

ジェネレータ式

内包表記と似たものとして、ジェネレータ式というものがあります。 リスト内包表記の []() に置き換えれば、ジェネレータ式になります。 ジェネレータ式は、4-2で説明したイテレータを構築します。 4-2で説明したように、イテレータは、for文で走査(全要素を訪問)できます。

[20]:
it = (x * 3 for x in 'abc')
for x in it:
    print(x)
aaa
bbb
ccc

イテレータを組み込み関数 list()tuple() に渡すと、対応するリストやタプルが構築されます。 なお、ジェネレータ式を直接引数とするときには、ジェネレータ式の外側の () は省略可能です。

[21]:
list(x ** 2 for x in range(5))
[21]:
[0, 1, 4, 9, 16]
[22]:
tuple(x ** 2 for x in range(5))
[22]:
(0, 1, 4, 9, 16)

総和を計算する組み込み関数 sum() など、リストやタプルを引数に取れる大抵の関数には、イテレータも渡せます。

[23]:
sum(x ** 2 for x in range(5))
[23]:
30

上の例において、ジェネレータ式の代わりにリスト内包表記を用いても同じ結果を得ますが、 計算の途中で実際にリストを構築するので、メモリ消費が大きいです。 ジェネレータ式では、リストのように走査できるイテレータを構築するだけなので、リスト内包表記よりメモリ効率がよいです。 したがって、関数に渡すだけの一時オブジェクトには、リスト内包表記ではなくジェネレータ式を用いるのが有効です。

練習の解答

[24]:
strings = ['The', 'quick', 'brown']
[len(x) for x in strings]
[24]:
[3, 5, 5]
[25]:
str1 = '123,45,-3'
[int(x) for x in str1.split(',')]
[25]:
[123, 45, -3]
[26]:
def var(lst):
    n = len(lst)
    av = sum(lst)/n
    return sum([(x - av)*(x - av) for x in lst])/n
[27]:
def var(lst):
    n = len(lst)
    av = sum(lst)/n
    return sum([x*x for x in lst])/n - av*av
[28]:
def sum_lists(list1):
    return sum([sum(lst) for lst in list1])
[29]:
def sum_matrix(list1,list2):
    return [[list1[i][j] + list2[i][j] for j in range(3)] for i in range(3)]
[ ]: