🐍

Pythonならではの書き方を理解する~ :(コロン)とリスト内包表記 ~

2021/11/23に公開

はじまり

わたし「ねえねえDSくん、久しぶりにPythonかいたんだよ、みてみて!」
DSくん「がんばったね!!でもそれ、半分くらいの行数で書けるよ」
わたし「な、ナンダッテー!」
DSくん「ほれ」
わたし「なにこの配列の初期化方法…」

※ DSくんはAIひとすじピー年、わたしは広く浅くWEBエンジニアです

わたしがかいたもの

img = cv2.imread("./aza.png")
COLOR_NAME = ("blue", "green", "red")
height, width, _ = img.shape
blue = []
green = []
red = []

for i in range(height):
    for j in range(width):
        blue.append(img[i, j][0])
        green.append(img[i, j][1])
        red.append(img[i, j][2])

sums = [sum(blue), sum(green), sum(blue)]
maxIndex = sums.index(max(sums))

print(COLOR_NAME[maxIndex])

DSくんがかいたもの

img = cv2.imread("./aza.png")
COLOR_NAME = ("blue", "green", "red")

sums = [img[:, :, ch].sum() for ch in range(img.shape[2])]
maxIndex = sums.index(max(sums))

print(COLOR_NAME[maxIndex])

DSくんがかいたものを受け取ったわたし

『DS先生のよくわかる解説のコーナーを所望します!!!!!!』
というわけで今回はDS先生の許可をもらった上で、弟子のあざらしによる解説です

[img[:, :, ch].sum() for ch in range(img.shape[2])] をゆるりと解読していく

img[:, :, ch]

  • numpyにおけるlist[i][j][k]list[i, j, k]list[i, j][k]はすべて同じ意味である
  • pythonにおける:は基本的には『範囲』を示す。1:3にすると『1以上3未満』である
    • そして:のみにした時は『最初から最後まですべて』という意味になる (試1)
  • なので、img[:, :, 0]だけで『imgの1個目と2個目の番地番号すべての、3個目の先頭だけ』を抜き出す処理になる(試2)
試1: rangeおためし

試1: rangeおためし

コード

listA = list(range(5))
print(listA)
print(listA[1:3])
print(listA[:])

出力

[0, 1, 2, 3, 4]
[1, 2]
[0, 1, 2, 3, 4]
試2: [:, :, 0]おためし

試2: [:, :, 0]おためし

コード

listA = np.arange(27).reshape((3, 3, 3))
print(listA)
print("---")
print(listA[:, :, 0])

出力

  • 先頭の3の倍数の数のみ抜き出されていることがわかる
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]]]
---
[[ 0  3  6]
 [ 9 12 15]
 [18 21 24]]

[ch] for ch in range(img.shape[2])

  • 『リスト内包表記』という配列の初期化手法のひとつらしい
  • img.shape[2]はOpenCVの処理で画像の形状が入ってる。今回はカラー画像なので3。(モノクロのときは1になるはず)
    • ch in range(img.shape[2])で3回繰り返す配列になる
    • これを式 for 変数名 in 配列という書き方をすると、式で元の配列を処理した配列が生成できる(試3)
試3: リスト内包表記おためし

試3: リスト内包表記おためし

コード

print(list([ch * 2] for ch in range(img.shape[2])))
print(list([(ch + 1) * 2] for ch in range(img.shape[2])))

出力

[[0], [2], [4]]
[[2], [4], [6]]

[img[:, :, ch].sum() for ch in range(img.shape[2])]

  • sumはただのsumなので飛ばして、と…。
  1. リスト内包表記によってchは0, 1, 2と変化する。配列の結果はきっと3行。
  2. img[:, :, ch].sum()から、同ch内の値(画素値)をすべて合算したものが結果に入る
  • という意味になります。わかってしまえば簡単2ステップで今なら自分でこの形にリファクタするもですが、最初は『どこで区切るかわからないし、なんか謎の記号(:)があるよ???』ってなってました。

おわり

わたし「ところで、ch、宣言の前に利用してるけど逐次処理的に大丈夫なの?」
DSくん「そう思った時期が自分にもありました」

補足: appendは使わない方がいい

  • 実は話のはじまりは『まず、appendは使わない方がいい』でした
  • appendめっちゃ遅いらしくて、最初みたいにbgrの配列にわけたい…の場合は、最低限『width, heightに基づいて配列領域を確保してから、書き換えていく』みたいな処理が推奨らしいです

つぶやき

  • WEBサービスでは滅多に2次元3次元の配列は扱わないので、配列に関してこんなに色んな記法があるのはPythonならではな感じがして面白いですね。PythonはAIに強いイメージありますが、AIに強い ≒ 画像処理や大規模データに強いなので、こういう発展をしたんだろな、と。
  • 『よく使うわけでもないが、GETの記載が長いのでわかりやすい名前で定数化する』を業務ではよくやるんですが、Pythonでは大文字にすることでしかそれを縛れないのがちょっと不安を感じてしまう…。

Discussion