Python入門 全人類がわかる内包表記

 2019/11/24    Python    

はじめに

Pythonには内包表記と呼ばれる記法があります。[ ]を使って一行で書かれているコードを見たことはありませんか?可読性が下がってしまうデメリットがありますが、処理速度が速くなるなどの理由から内包表記を使って書かれることはあります。今回はその内包表記について説明していきます!

基本的な書き方

内包表記の基本的な書き方は、次のようになります!内包表記を使うとfor文が一行で書くことが出来ます。
内包表記は次のように書くことが出来ます。※本記事ではリスト内包表記と内包表記は区別せずに説明します。

 list_example = [iの処理 for i in 配列]

例として0~9までの数字の2乗をlist_1に追加するコードを内包表記で書いてみましょう。

list_1 = [i**2 for i in range(10)]#0~9までの数字の2乗をlist_1に追加する
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

内包表記を使わずに書くと次のようになります。

list_1 = []
for i in range(10):
    i = i**2
    list_1.append(i)

短いコードですが、普通に書くと4行くらい書かなければいけないのでかなり行数の省略になることがお分かりいただけると思います。

内包表記内では変数を代入することが出来ません。
もし変数代入を含んだまま実行するとSyntaxErrorになるので気を付けてください。

 list_example = [i*10 i = input("iの数字は?")]#input()で数字を受け取って10倍しようとしたコード
SyntaxError: invalid syntax #無念のエラー

for文

最初の例でも示したようにfor文は内包表記ですっきり書くことが出来ます。

内包表記でのfor文の書き方

for文は次のようにすると内包表記で書くことが出来ます。

 list_example = [iの処理 for i in 配列]

1~10までの数字で2の倍数のものだけをlist_2に入れてみましょう。

list_2 = [i for i in range(2,11,2)]
[2, 4, 6, 8, 10]

2重のループの書き方

内包表記を使って2重ループを書くことも出来ます。2重ループについて復習したい方はこちら。
for文が2つあるときの書き方は次のようになっています。

 list_example = [処理 ループ1 ループ2]

ループ1から実行されていきます。実行順序としてはループ1が1回実行される間にループ2が全部回ります。
2重ループを用いた演習としてformat関数の記事でひたすらやった九九を作成する問題を後ほどやりましょう!

また、2つ以上のfor文を使えることから2次元以上のリストを簡単に作ることが出来ます。


[[i,j] for i in range(10) for j in range(10)]

for文の演習

for文を内包表記を使って書く練習をしましょう!

[演習]内包表記とfor文を2つ使って実行結果が以下となるような九九を作るプログラムを作成しましょう。

1x1=1
1x2=2
・・・(省略)
9x7=63
9x8=72
9x9=81

この問題では内包表記とfor文以外にもformat関数も使うので、わからない人はこちらの記事も参考にしてみてください。

いかがでしょうか?
解答例は次のようになってきます!(可読性が下がることを体感していただけるコードが出来ました)

list_3 = [print("{}x{}={}".format(i,j,i*j)) for i in range(1,10) for j in range(1,10)]

[演習]list_news = ["North","East","West","South"]の頭文字だけを1つの変数に格納してください。

こちらの問題はリスト内の要素の頭文字をどのように取得するかが肝になっております。
ちなみに出力結果は次のようになります。

['N', 'E', 'W', 'S']
joinメソッドを使って綺麗に結合すると次のようになります。
['NEWS']

いかがでしょうか?

解答例は次のようになっております!

list_news = ["North","East","West","South"]
ini = ([dir[0] for dir in list_news]) #変数dirに各要素を格納
['N', 'E', 'W', 'S']

joinメソッドを使って綺麗に結合する。
list_news = ["North","East","West","South"]
ini = "".join([dir[0] for dir in list_news])
['NEWS']

if文

if文も内包表記を使って書くことが出来ます。if文だけを使った内包表記にいい例が思いつかなかったので、for文とif文を組み合わせたものを例とします。

内包表記でのif文の書き方

if文を内包表記で書くと次のようになります!例としてお馴染みの0~9の数字のうちの偶数のものだけを取得するコードを書きます。

 list_example = [iの処理 for i in 配列 if 判定式]
list_4 = [i for i in range(10) if i % 2 ==0]

内包表記は左から実行されていくイメージなのでfor文とif文の順番が反転するとエラーになってしまいます。※例外あり

内包表記での複数のif文の書き方

もちろん内包表記の中にif文を複数入れることも出来ます。

 list_example = [i for i in range(10) if i % 2 == 0 i % 3 == 0]
[0, 6]

if~else文(三項演算子)

さて、内包表記も三項演算子を使った書き方が出来ます。先ほどのif文の判定式の部分を三項演算子に置き換える.....わけではありません。
どちらかといえば、"iの処理"の部分が三項演算子で表されます。

 list_example = [三項演算子 for i in 配列]

演習として次の問題をやってみましょう!

[演習]1~10の数字の偶数と奇数を判別するプログラムを内包表記を用いて書いてください。

['odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even']

三項演算子の書き方は

(条件がTrueのときの値) if (条件) else (条件がFalseのときの値)

こちらを参考にしながら解いて見てください!

いかがでしょうか?
以下に解答例を示します。

list_5 = ["even" if i % 2 == 0 else "odd" for i in num]

内包表記の複数処理

(複数処理とは...)
内包表記を使って複数処理する方法を紹介します。
複数の条件分岐をする時はif文の章で紹介した方法で記述してください。
複数のリストやその要素を扱うためには、組み込み関数を使う必要があります。今回使用する組み込み関数については別記事でかなり詳しく書いているので是非読んでみてください。Python入門 for文に便利な関数をまとめてみた!(enumerate関数,zip関数編)
この記事では簡単に内包表記での書き方を紹介します。

内包表記とenumerate関数

enumerate関数はリスト型を[インデックス,要素]のような形にしたい時に使う関数です。
例えば、元素と元素番号を紐ずけて表示したいときは

elements = ["hydrogen","helium","Lithium","Beryllium","Boron"]
for index,element in enumerate(elements,1): #enumerate関数の第2引数で開始インデックスを決められる。
    print("{}: {}".format(index,element))

このように書けます。これを内包表記で書くと...

elements = ["hydrogen","helium","Lithium","Beryllium","Boron"]
["{}: {}".format(index,element) for index,element in enumerate(elements,1)]

という風に書けます。どちらも出力結果は次の例に近いかたちになります。

1: hydrogen
2: helium
3: Lithium
4: Beryllium
5: Boron

内包表記とzip関数

zip関数は複数のリストの要素を同時に取り出してくれる関数です。
今回は元素の英語と日本語を出力するコードを書きました。

#内包表記を使わない場合
elements = ["hydrogen","helium","Lithium","Beryllium","Boron"]
jpns = ["水素","ヘリウム","リチウム","ベリリウム","ホウ素"]
for element,jpn in zip(elements,jpns):
    print("{}: {}".format(element,jpn))
#内包表記を使った場合
[print("{}: {}".format(element,jpn)) for element,jpn in zip(elements,jpns)]

どちらも出力結果は次のようになります。

hydrogen: 水素
helium: ヘリウム
Lithium: リチウム
Beryllium: ベリリウム
Boron: ホウ素

演習

解説の締めくくりとして、FizzBuzz問題を内包表記を使って書いてみましょう!

FizzBuzz問題

1から50までの数字を出力するプログラムを書いてください。
ただし、数字が3の倍数の時は数字の代わりにFizzと出力し
5の倍数の時は数字の代わりにBuzzと出力し
3と5の倍数のときは、FizzBuzzと出力すること。

この演習問題では三項演算子を使って1行で書きます。条件式が複数回出てくるのでネストされた三項演算子を使ってください。

(条件1がTrueのときの値) if (条件1) else (条件2がTrueのときの値) if (条件2) else (条件3がTrueのときの値) if (条件3) else (条件3がFalseのときの値)

ネストされた三項演算子さえ書ければ、この問題は解けると思います。
と、同時に恐ろしい程可読性が下がっていることもポイントです。(戒め)

いかがでしょうか??

解答例は以下のようになります。

["FizzBuzz" if n % 15 == 0 else "Fizz" if n % 3 == 0 else "Buzz" if n % 5 == 0 else n for n in range(1,51)]

 

内包表記のメリット

さて、今回の記事では内包表記について解説してきました。
今まではひたすら可読性が下がることを体感していただいたと思いますが、内包表記を使うメリットはどこにあるのでしょうか?
一般的に内包表記はfor文よりも処理速度が速くなる。と言われています。それらについて詳しく見ていきましょう!

速度について

まずは、for文と内包表記の速度について調べていきましょう!


def code_loop(n): #ループ処理をする関数の定義
    L = []
    for i in range(n):
        L.append(i)
    return L
def code_comprehension(n): #内包表記で処理する関数の定義
    return [i for i in range(n)]

%timeit code_loop(10000) #ループ処理にかかる時間の計算
%timeit code_comprehension(10000) #内包表記の処理にかかる時間の計算

774 µs ± 4.04 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) #for文の処理にかかった時間
374 µs ± 1.82 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) #内包表記の処理にかかった時間

見ていただいてもわかるように、内包表記の時の方がfor文を使った時よりも2倍近く処理にかかる時間が短いです。
確かに、噂通り処理速度は内包表記の方が速いことがわかりました。
では、何故内包表記の方が処理速度が速いのか見ていきましょう!

差が出る理由

for文を使ってループ処理をした時、次の4ステップを経て新たにリストが生成されます。

  1. 空のリストが作成される。
  2. appendメソッドが呼び出される。
  3. appendメソッドに引数が渡される。
  4. 空のリストに要素が追加される。

一方、内包表記を用いた場合、次のようにして新たにリストが生成されます。

  1. 空のリストに要素を追加する。

for文を使って書いた時は、メソッドリストからappendを取り出してそれをメソッドとして呼び出す処理をしなければいけません。
それに対して、内包表記を使って書いた場合は直接リストに要素を追加することが出来ます。
単純にPythonに対する命令の数が減る。リストからメソッドを探さなくて良い。メソッドの呼び出しが不要になる。
という3つの理由で、内包表記の処理がfor文を使った時に比べて速くなると考えられます。

 

インデント

Pythonの内包表記とfor文を比べるとき、よく速度の話が出てきます。しかし、これらの話は処理に時間がかかる計算をするようになってから考えればいいと思っています。それよりも初学者を苦しめるのは、””インデント””です。
Pythonではインデントをよく使います。
皆様もご経験あるかと思いますが、
このインデントの位置を間違えるだけでも、その箇所の発見、修正でかなりの時間を要します。

内包表記を使うとインデントを使わなくて済むので、インデントのエラーが起こる確率も必然的に減ります。
内包表記を使ってインデントミスの修正に使う時間を減らして、勉強時間を効率的に使いましょう!

Numpyの紹介

for文や内包表記の話をしてきましたが、似たような処理をできるライブラリとしてNumpyというものが存在します。
「Pythonでデータ解析をするなら、とりあえずnumpy使う」と言われるように、かなり便利なライブラリとなっています。
ちなみに、for文、内包表記、Numpyを使って同じような処理をして最も処理の早いのはNumpyを使った時です。
全人類がわかる統計学のサイトで一度紹介の記事を出しているので、ぜひそちらも見てみてください!

まとめ

さて、今回は内包表記の入門的な内容についてまとめました。
lambda(ラムダ)式と同じく可読性が下がるデメリットがあるので、乱用は禁物です。
可読性が下がると、復習しにくくなったり、他の人に見てもらえないなどというデメリットがあります。
適材適所で使っていきましょう!

  • 人気の投稿とページ

  • コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です