あなたのVoAGIポスト-なぜPythonでリスト内包表記を過度に使用すべきではないか
美とファッションの専門家が伝授!あなたのVoAGIポスト-なぜPythonでリスト内包表記を過度に使用するべきではないのか
Pythonでは、リスト内包表記は既存のリストや他の反復可能なオブジェクトから新しいリストを短縮した構文で作成することができます。しかし、リスト内包表記に慣れてしまうと、適切でない場面でも使ってしまうかもしれません。
覚えておいてください、目標はシンプルでメンテナンスしやすいコードを書くことです。複雑なコードではありません。特に以下の内容を再確認することは有益です:Pythonの禅。これは、クリーンでエレガントなPythonを書くための箴言のセットです。
- 美しいものは醜いよりも良いです。
- シンプルさは複雑さよりも良いです。
- 可読性が重要です。
このチュートリアルでは、リスト内包表記を使用してコードが非常にメンテナンスしにくくなる3つの例をコーディングしてみましょう。その後、同じもののメンテナンスしやすいバージョンを書いてみましょう。
- 「C# で GPT(一般目的テンプレート)を拡張しましょう」
- ジェネラティブAIをマスターするための5つの無料コース
- LLM SaaSのためのFastAPIテンプレート パート1 — Authとファイルのアップロード
それではコードを書き始めましょう!
Pythonリスト内包表記: 簡単な復習
Pythonでリスト内包表記を復習しましょう。既存のリストや文字列などの反復可能オブジェクトがあるとします。そのオブジェクトから新しいリストを作成したい場合には、反復可能オブジェクトをループし、各アイテムを処理して新しいリストに出力を追加することができます。以下のようになります:
new_list = []for item in iterable: new_list.append(output)
しかし、リスト内包表記には同じことを行うための短縮した1行の代替手段もあります:
new_list = [output for item in iterable]
さらに、フィルタ条件を追加することもできます。
次のコード:
new_list = []for item in iterable: if condition: new_list.append(output)
は、次のリスト内包表記に置き換えることができます:
new_list = [output for item in iterable if condition]
したがって、リスト内包表記は視覚的なノイズを減らすことでPythonコードを書くのを助けます。
では、複雑な式が必要なタスクにリスト内包表記を使用してはいけない理由を理解するために、3つの例を見てみましょう。なぜなら、そのような場合には、リスト内包表記はコードをエレガントにする代わりに、読みにくくてメンテナンスしにくいコードになってしまうからです。
例1:素数の生成
問題: 数字上限
が与えられた場合、その数以下のすべての素数のリストを生成します。
この問題を次の2つのキーのアイデアに分解できます。
- 素数かどうかのチェック
- 素数のリストを作成する
このためのリスト内包表記の式は以下のようになります:
import mathupper_limit = 50 primes = [x for x in range(2, upper_limit + 1) if x > 1 and all(x % i != 0 for i in range(2, int(math.sqrt(x)) + 1))]print(primes)
そして、出力は以下のようになります:
Output >>>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
一見すると何が起こっているかわかりにくいです…改善しましょう。
もしかしたら、これはもっと良くなりましたか?
import mathupper_limit = 50 primes = [ x for x in range(2, upper_limit + 1) if x > 1 and all(x % i != 0 for i in range(2, int(math.sqrt(x)) + 1))]print(primes)
読みやすくなりましたね。さて、もっと良いバージョンを書いてみましょう。
改善版
リスト内包表記はこの問題を解決するのには良いアイデアですが、リスト内包表記で素数をチェックするロジックが雑になっています。
そのため、より保守性の高いバージョンを書いてみましょう。素数かどうかをチェックするロジックを別の関数is_prime()
に移動しましょう。そして、内包表記で関数is_prime()
を呼び出します:
import math
def is_prime(num):
return num > 1 and all(num % i != 0 for i in range(2, int(math.sqrt(num)) + 1))
upper_limit = 50
primes = [x for x in range(2, upper_limit + 1) if is_prime(x)]
print(primes)
出力 >>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
改善版は十分良いですか? これにより内包表記が理解しやすくなりました。今では、式がupper_limit
までのすべての素数(つまりis_prime()
がTrueを返す数)を収集していることが明確です。
例2:行列での作業
問題:行列が与えられた場合、次の情報を見つけ出します:
- すべての素数
- 素数のインデックス
- 素数の合計
- 降順に並べられた素数
行列をフラットにし、すべての素数のリストを収集するために、前の例と似たロジックを使用できます。
ただし、インデックスを見つけるには、別の複雑なリスト内包表記が必要です(コードの読みやすさのためにコードを整形しています)。
素数をチェックし、そのインデックスを1つの内包表記で組み合わせることもできますが、それは簡単になるわけではありません。
以下がコードです:
import math
from pprint import pprint
my_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
def is_prime(num):
return num > 1 and all(num % i != 0 for i in range(2, int(math.sqrt(num)) + 1))
# 行列をフラットにし、素数だけを含むようにフィルタリングする
primes = [x for row in my_matrix for x in row if is_prime(x)]
# 元の行列内で素数のインデックスを見つける
prime_indices = [(i, j) for i, row in enumerate(my_matrix) for j, x in enumerate(row) if x in primes]
# 素数の合計を計算する
sum_of_primes = sum(primes)
# 素数を降順でソートする
sorted_primes = sorted(primes, reverse=True)
# 結果を辞書として作成する
result = {"primes": primes, "prime_indices": prime_indices, "sum_of_primes": sum_of_primes, "sorted_primes": sorted_primes}
pprint(result)
次に、対応する出力があります:
出力 >>> {'primes': [2, 3, 5, 7], 'prime_indices': [(0, 1), (0, 2), (1, 1), (2, 0)], 'sum_of_primes': 17, 'sorted_primes': [7, 5, 3, 2]}
では、改善版とは何でしょうか?
改善版
より良いバージョンでは、関心ごとを分離するために一連の関数を定義することができます。これにより、問題が発生した場合にどの関数に戻ってロジックを修正するかがわかります。
import mathfrom pprint import pprintdef is_prime(num): return num > 1 and all(n % i != 0 for i in range(2, int(math.sqrt(num)) + 1))def flatten_matrix(matrix): flattened_matrix = [] for row in matrix: for x in row: if is_prime(x): flattened_matrix.append(x) return flattened_matrixdef find_prime_indices(matrix, flattened_matrix): prime_indices = [] for i, row in enumerate(matrix): for j, x in enumerate(row): if x in flattened_matrix: prime_indices.append((i, j)) return prime_indicesdef calculate_sum_of_primes(flattened_matrix): return sum(flattened_matrix)def sort_primes(flattened_matrix): return sorted(flattened_matrix, reverse=True)my_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]primes = flatten_matrix(my_matrix)prime_indices = find_prime_indices(my_matrix, primes)sum_of_primes = calculate_sum_of_primes(primes)sorted_primes = sort_primes(primes)result = { "primes": primes, "prime_indices": prime_indices, "sum_of_primes": sum_of_primes, "sorted_primes": sorted_primes}pprint(result)
このコードは以前と同じ出力を生成します。
Output >>>{'primes': [2, 3, 5, 7], 'prime_indices': [(0, 1), (0, 2), (1, 1), (2, 0)], 'sum_of_primes': 17, 'sorted_primes': [7, 5, 3, 2]}
この改良版は十分に良いですか?この例のような小さな行列では動作しますが、静的なリストを返すことは一般的にはお勧めできません。より大きな次元に汎用化するためには、ジェネレーターを使用することができます。
例3:ネストされたJSON文字列の解析
問題: 条件に基づいて与えられたネストされたJSON文字列を解析し、必要な値のリストを取得します。
ネストされたJSON文字列の解析は難しいです。ネストのレベルの違い、JSONの動的な性質、パースのロジック内でのさまざまなデータ型を考慮する必要があります。
具体的な例として、与えられたJSON文字列を条件に基づいて解析し、次のような値のリストを取得することを考えます:
- 整数または整数のリスト
- 文字列または文字列のリスト
JSON文字列をPythonの辞書に変換するには、組み込みのjsonモジュールのloads
関数を使用します。その後、ネストされた辞書に対してリスト内包表記を使用します。
リスト内包表記では、ネストされた辞書を反復処理するためにネストされたループを使用します。各値に対して、次の条件に基づいてリストを構築します:
- 値が辞書ではなく、キーが ‘inner_key’ で始まる場合、
[inner_item]
を使用します。 - 値が ‘sub_key’ を持つ辞書の場合、
[inner_item['sub_key']]
を使用します。 - 値が文字列または整数の場合、
[inner_item]
を使用します。 - 値が辞書の場合、
list(inner_item.values())
を使用します。
以下のコードスニペットをご覧ください:
import jsonjson_string = '{"key1": {"inner_key1": [1, 2, 3], "inner_key2": {"sub_key": "value"}}, "key2": {"inner_key3": "text"}}'# JSON文字列をPythonの辞書に変換するdata = json.loads(json_string)flattened_data = [ value if isinstance(value, (int, str)) else value if isinstance(value, list) else list(value) for inner_dict in data.values() for key, inner_item in inner_dict.items() for value in ( [inner_item] if not isinstance(inner_item, dict) and key.startswith("inner_key") else [inner_item["sub_key"]] if isinstance(inner_item, dict) and "sub_key" in inner_item else [inner_item] if isinstance(inner_item, (int, str)) else list(inner_item.values()) )]print(f"Values: {flattened_data}")
これが出力です:
出力>>>値:[[1, 2, 3]、'value'、'text']
わかるように、リスト内包表記は非常に理解しづらいです。
自分自身とチームの他の人のためには、そのようなコードを書かないようにしてください。
より良いバージョン
ネストされたforループとif-elifの組み合わせを使用した以下のスニペットのほうが良いと思います。なぜなら、何が起こっているかがわかりやすいからです。
flattened_data = []for inner_dict in data.values(): for key, inner_item in inner_dict.items(): if not isinstance(inner_item, dict) and key.startswith("inner_key"): flattened_data.append(inner_item) elif isinstance(inner_item, dict) and "sub_key" in inner_item: flattened_data.append(inner_item["sub_key"]) elif isinstance(inner_item, (int, str)): flattened_data.append(inner_item) elif isinstance(inner_item, list): flattened_data.extend(inner_item) elif isinstance(inner_item, dict): flattened_data.extend(inner_item.values())print(f"値:{flattened_data}")
これも期待された出力を提供します:
出力>>>値:[[1, 2, 3]、'value'、'text']
より良いバージョンは十分に良いですか? 実は、そうではありません。
なぜなら、if-elifの組み合わせはしばしばコードのにおいと考えられるからです。分岐ごとにロジックを繰り返し、条件を追加するとコードがさらに保守しづらくなります。
ただし、この例では、リスト内包表現よりもif-elifの組み合わせとネストされたループのほうが理解しやすいです。
まとめ
これまでにコーディングした例を通じて、リスト内包表記のようなPythonの特徴を過度に使用することが時には良いことになりすぎることについてのアイデアをお伝えしました。これはリスト内包表記に限らず(最も頻繁に使用されるものですが)、辞書やセット内包表記にも当てはまります。
常に理解しやすく、保守しやすいコードを書くように心がけてください。Pythonicな特徴を使わないということでも、シンプルに保つように努めてください。コーディングを続けましょう!
[Bala Priya C](https://twitter.com/balawc27)は、インドの開発者兼テクニカルライターです。彼女は数学、プログラミング、データサイエンス、コンテンツ作成の交差点での仕事が好きです。彼女の関心と専門知識の分野には、DevOps、データサイエンス、自然言語処理が含まれます。彼女は読書、執筆、コーディング、コーヒーを楽しんでいます!現在、彼女はチュートリアル、ハウツーガイド、意見記事などを執筆して開発者コミュニティとの知識共有を進めています。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles