ラムダと内包表記
これら3つの機能には共通点があります。本来なら複数行を要するアイデアを、1つの読みやすい式で表現できるのです。うまく使えば、コードはより短く、より明確になります。下手に使えば、読めなくなります。この章では、それぞれをいつ使うべきか、そしていつ使うのをやめるべきかを扱います。
ラムダ関数
ラムダは、名前のない、単一式の関数です。lambda キーワードで作成します。その本当の有用性は、名前付き関数を先に定義することなく、必要な場所にインラインで書けることです。これが sorted() で役立つ理由です。
double = lambda x: x * 2
double(5) # 10これは以下と等価です:
def double(x):
return x * 2ほとんどの場合、def を使ってください。ラムダには本当の利点が1つあります。名前を付けずに、必要な場所にインラインで書けることです。これが sorted()、map()、filter() で役立つ理由です:
players = [("さくら", 87), ("ひろし", 74), ("ゆうき", 92)]
sorted(players, key=lambda p: p[1]) # スコアでソート(昇順)
sorted(players, key=lambda p: p[1], reverse=True) # スコアでソート(降順)ラムダがなければ、key= 引数のためだけに名前付き関数を定義しなければなりません。ラムダは意図をローカルに、目に見える形で保ちます。
ラムダは複数の引数を取ることができます:
add = lambda a, b: a + b
add(3, 4) # 7ラムダを使うとき: 1か所だけで使う単純な式の場合のみ。複雑になっている、または再利用が必要な場合は、ちゃんとした def を書いてください。複数の演算子にまたがる、または条件分岐が必要なラムダは、通常 def に切り替えるべきサインです。
リスト内包表記
Pythonで最も一般的な変換は、シーケンスを取って、各要素に対して何かを行い、新しいリストを取得することです。リスト内包表記は、これを1行の読みやすい形で行います: [expression for item in iterable]。if でフィルタを追加することもできます。
長い書き方:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)リスト内包表記:
squares = [n ** 2 for n in numbers]構造は常に同じです: [expression for item in iterable]。
scores = [87, 42, 96, 55, 71]
scaled = [s * 1.1 for s in scores] # 10%のボーナスを適用
as_grades = [f"{s}/100" for s in scores] # それぞれをフォーマット条件付きフィルタリング
if 句を追加すると、テストに合格する要素だけを含めることができます。結果は、条件が True の要素だけを含む新しいリストです。
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [n for n in numbers if n % 2 == 0] # [2, 4, 6, 8]
odds = [n for n in numbers if n % 2 != 0] # [1, 3, 5, 7]scores = [87, 42, 96, 55, 71, 38]
passing = [s for s in scores if s >= 60] # [87, 96, 71]
failing = [s for s in scores if s < 60] # [42, 55, 38]ネストした内包表記
内包表記をネストすることで、リストのリストを単一のリストに平坦化できます。左から右に読みます: 各行について、その行の各要素について、その要素を含めます。
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]左から右に読みます: matrixの各rowについて、rowの各itemについて、itemを含める。
ネストした内包表記はすぐに混乱してしまうことがあります。解釈に少しでも時間がかかるなら、ループを明示的に書いてください。
辞書内包表記
辞書内包表記は、リスト内包表記と同じ考え方を使って、1つの式で辞書を構築します: {key: value for item in iterable}。リスト内包表記と同じように if でフィルタを追加できます。
names = ["さくら", "ひろし", "ゆうき"]
scores = [87, 74, 92]
score_map = {name: score for name, score in zip(names, scores)}
# {"さくら": 87, "ひろし": 74, "ゆうき": 92}フィルタ付き:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"さくら": 87, "ゆうき": 92}words = ["apple", "banana", "cherry"]
word_lens = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}セット内包表記
セット内包表記は、波括弧を使い、コロンなしで1つの式でセットを構築します。結果がセットなので、重複は自動的に削除されます。
words = ["apple", "banana", "cherry", "apple"]
unique = {w.lower() for w in words} # {"apple", "banana", "cherry"}一意の値が欲しく、順序を気にしないときに、セット内包表記を使ってください。
ジェネレータ式
ジェネレータは、角括弧の代わりに丸括弧を使うリスト内包表記のように見えます。重要な違いは、リスト内包表記はリスト全体を一度にメモリに構築することです。ジェネレータは、必要なときだけ、1つずつ値を生成します。大きなシーケンスの場合、これははるかに少ないメモリで済みます。
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum() がジェネレータを消費するsum()、max()、min()、any() などの関数に直接ジェネレータを渡すときは、追加の括弧を省略できます:
total = sum(n ** 2 for n in range(1000)) # 括弧は2組ではなく1組ほとんどの日常的なコードでは、リスト内包表記で十分です。大きなデータセットを処理する場合や、すべてをメモリに保持するのが無駄になるようなストリーミングデータを扱う場合に、ジェネレータを使ってください。
zip()
zip() は2つ以上のシーケンスから要素をペアにして、並列にループできるようにします。最短のシーケンスで停止します。2つのリストが互いに対応している場合に、インデックスの管理を避けるきれいな方法です。
names = ["さくら", "ひろし", "ゆうき"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# さくら: 87
# ひろし: 74
# ゆうき: 92zip() は最短のシーケンスで停止します。シーケンスの長さが異なる可能性がある場合は、itertools.zip_longest() を埋め値とともに使ってください。
ペアのzipされたリストを2つの個別のリストに戻すには、zip(*pairs) を使います:
pairs = [("さくら", 87), ("ひろし", 74), ("ゆうき", 92)]
names, scores = zip(*pairs)
# names = ("さくら", "ひろし", "ゆうき")
# scores = (87, 74, 92)* はここで何をしているのか?
*pairs はリストを個別の引数に展開します: zip(*pairs) は zip(("さくら", 87), ("ひろし", 74), ("ゆうき", 92)) になります。* 演算子については 関数 の章で扱います。
zip() は、インデックスを手動で管理することなく、複数のシーケンスを並列に反復するきれいな方法でもあります:
before = [10, 20, 30]
after = [15, 18, 35]
for b, a in zip(before, after):
change = a - b
print(f"{b} -> {a} ({'+' if change >= 0 else ''}{change})")map() と filter()
map() と filter() は、内包表記と同じことをする古い関数型スタイルのツールです。古いコードで目にすることがあるので、その意味を知っておく価値があります。新しいコードでは内包表記を優先してください。ほとんどのPython開発者にとってより読みやすいです。
numbers = [1, 2, 3, 4, 5]
list(map(lambda x: x ** 2, numbers)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]内包表記を優先してください。ほとんどのPython開発者にとってより読みやすいです。すでに存在する名前付き関数があるときは map() を使ってください:
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3](ここでは内包表記よりきれい)実践
プレイヤーリストを合格点でフィルタし、sorted とラムダでスコア順にランク付けし、列挙された順位とともに出力します:
players = [
{"name": "さくら", "score": 87},
{"name": "ひろし", "score": 42},
{"name": "ゆうき", "score": 96},
{"name": "たけし", "score": 55},
]
passing = [p for p in players if p["score"] >= 60]
ranked = sorted(passing, key=lambda p: p["score"], reverse=True)
score_map = {p["name"]: p["score"] for p in ranked}
for i, (name, score) in enumerate(score_map.items(), start=1):
print(f"{i}. {name}: {score}")
