Pythonで複数のデータを順番に管理するにはリスト(list)を使います。
本稿では、リストの中に特定の要素が含まれるかを素早く安全に判定できるin演算子に焦点を当て、基本の使い方から内部挙動、実用パターン、注意点とベストプラクティスまでを順を追って解説します。
Pythonのin演算子の基本
リストに値が含まれるかを判定する書き方
in演算子は、左辺の値が右辺のリストに含まれるかどうかを真偽値で返します。
最も基本的な書き方は次のとおりです。
# リストを用意します
fruits = ["apple", "banana", "orange"]
# "banana" がリストに含まれるか確認
print("banana" in fruits) # True になるはず
# "grape" は含まれていないので False
print("grape" in fruits)
True
False
inはリストの各要素と左辺の値を順に比較し、どれか1つでも一致すればTrue、1つも一致しなければFalseを返します。
戻り値は真偽値(bool)です。
not inで含まれないことを判定
含まれていないことを確認したい場合は、not inを使います。
fruits = ["apple", "banana", "orange"]
# "grape" が含まれていないことを確認
print("grape" not in fruits) # True
# "banana" は含まれているので False
print("banana" not in fruits)
True
False
not inはinの否定です。
条件分岐や入力検証で「禁止リストに含まれていないか」を確かめるときに便利です。
if文での真偽値の使い方
inはそのままifの条件にできます。
追加の比較は不要です。
fruits = ["apple", "banana", "orange"]
if "banana" in fruits:
print("banana はあります")
else:
print("banana はありません")
# 悪い例: 不要な比較は書かない
flag = ("banana" in fruits)
# if flag == True: # 冗長
# ...
# 良い例: そのまま条件に使う
if flag:
print("フラグは True です")
banana はあります
フラグは True です
inはboolを返します。
if (x in lst) == True
のような書き方は冗長なので避け、if x in lst:
と簡潔に書くのが読みやすさと保守性の面で推奨です。
リストでのin演算子の挙動
比較は==で評価される
inは要素との比較に等値比較(==)を用い、オブジェクトの同一性(is)ではありません。
そのため、型間の等価性や特殊値の性質が影響します。
import math
from decimal import Decimal
print(1 in [True, 2]) # True: 1 == True は True
print(True in [1, 2]) # True: True == 1 は True
print("1" in [1, "1", 2]) # True: 文字列 "1" は "1" と一致
print("1" in [1, 2]) # False: 文字列 "1" と数値 1 は一致しない
nan1 = float("nan")
nan2 = float("nan")
print(nan1 == nan2) # False: NaN 同士は == でも一致しない
print(nan1 in [nan2]) # False: in も == に従うため一致しない
print(Decimal("1.0") in [1, 2, 3]) # True: Decimal と int は数値的に等しい
True
True
True
False
False
False
True
boolはintのサブクラスで、True == 1
、False == 0
が成り立ちます。そのため混在に注意が必要です。
文字列と数値は異なる型なので通常は一致しません。
NaNはどの値とも等しくない性質を持つため、同じNaNどうしでもin
で一致しません。
Decimalは数値としての等価性で比較されるため、Decimal("1.0") == 1
はTrueになります。
参考として、いくつかの式と結果を整理します。
式 | 結果 | 理由 |
---|---|---|
1 in [True] | True | 1 == True がTrue |
True in [1] | True | True == 1 がTrue |
"1" in [1] | False | 文字列と数値は一致しない |
float("nan") in [float("nan")] | False | NaN同士でも== はFalse |
探索は先頭から順に行われる
リストに対するinは先頭から順に要素を比較し、最初に一致した時点で探索を打ち切ります。
以下はその様子を可視化する例です。
# 比較の過程を出力するクラス
class EchoEq:
def __init__(self, value):
self.value = value
def __eq__(self, other):
o = getattr(other, "value", other)
print(f"== を評価: {self.value} == {o}")
return self.value == o
def __repr__(self):
return f"EchoEq({self.value})"
items = [EchoEq(1), EchoEq(2), EchoEq(3)]
target = EchoEq(3)
print(target in items) # 先頭から順に == が呼ばれる
== を評価: 1 == 3
== を評価: 2 == 3
== を評価: 3 == 3
True
上から順に==
が呼ばれ、3つ目で一致した瞬間に探索が終了しています。
先頭付近に候補があるほど速く、末尾や存在しない場合は遅くなります。
文字列は大文字小文字を区別する
文字列の比較はデフォルトで大文字小文字を区別します。
必要に応じて正規化しましょう。
fruits = ["apple", "Banana", "ORANGE"]
# 素直な比較(区別あり)
print("apple" in fruits) # True
print("Apple" in fruits) # False
# 小文字に正規化して比較(区別なし)
needle = "Apple"
print(needle.lower() in [s.lower() for s in fruits]) # True
# anyを使った等価な書き方
print(any(s.lower() == "apple" for s in fruits)) # True
True
False
True
True
ケースインセンシティブにしたいときは、比較前に両者を同じ形式(例: 全て小文字)に正規化するのが基本です。
in演算子の実用パターン
完全一致でリストの要素を判定
リストのinは「要素の完全一致」を判定します。
部分文字列の判定と混同しないようにしましょう。
colors = ["red", "green", "blue"]
# 完全一致
print("red" in colors) # True
# 部分一致はしない
print("re" in colors) # False
# 参考: 文字列同士の部分一致は文字列の in
print("py" in "python") # True (これは文字列の話)
True
False
True
"re" in colors
はFalseですが、"py" in "python"
はTrueです。
前者は「リストの要素として存在するか」、後者は「文字列に部分として含まれるか」という別の話です。
ネストしたリストの存在チェック
ネストしたリスト(リストの中にリストが入っている)では、inは「トップレベルの要素」に対して働きます。
内側の要素まで自動的に探しません。
nested = [[1, 2], [3, 4], [5, 6]]
# サブリスト自体が要素として存在するか
print([3, 4] in nested) # True
# 2 がどれかの内側リストに含まれるか(トップレベルでは見つからない)
print(2 in nested) # False
# 内側まで探したい場合は any を使う
print(any(2 in inner for inner in nested)) # True
# すべての内側リストに 2 が含まれるかを調べるなら all
print(all(2 in inner for inner in nested)) # False
True
False
True
False
内側まで見たいときは明示的にループ、内包表記、any
/all
を使います。
トップレベルのリストに対してin
を使うだけでは、内側のスカラー要素までは届きません。
複数候補の存在チェック
「1つの値が複数の候補に含まれるか」と「複数の候補のうち1つでもリストに存在するか」の2つの状況を区別します。
value = "png"
# 1) 単一の値が複数候補に含まれるか
allowed = ["jpg", "jpeg", "png", "gif"]
if value in allowed:
print("許可された拡張子です")
# 2) 複数候補のうち1つでもリストにあるか
installed = ["numpy", "pandas", "requests"]
candidates = ["httpx", "requests", "urllib3"]
if any(pkg in installed for pkg in candidates):
print("候補のうち少なくとも1つはインストール済みです")
# 3) 大量の候補を高速に判定したい場合は set を使って集合演算
installed_set = set(installed)
candidates_set = set(candidates)
if installed_set & candidates_set: # 積集合が空でなければ何かが共通
print("集合の積で共通要素が見つかりました")
許可された拡張子です
候補のうち少なくとも1つはインストール済みです
集合の積で共通要素が見つかりました
候補が多い場合や繰り返し判定する場合は、set
に変換してから集合演算(積集合、部分集合など)を使うと簡潔で高速です。
in演算子の注意点とベストプラクティス
型の違いによる一致に注意
数値と文字列、boolとint、Decimalなど、型ごとの等価性は異なります。
意図しない一致や不一致を避けるため、比較前に型や形式をそろえるのが安全です。
from decimal import Decimal
values = [0, 1, 2, "3", Decimal("4.0")]
print("1" in values) # False: 文字列 "1" と数値 1 は一致しない
print(True in values) # True: True == 1
print(Decimal("4") in values) # True: Decimal(4) と Decimal("4.0") は数値的に等しい
# 型をそろえる例(すべて文字列に変換して比較)
needle = "1"
values_str = [str(v) for v in values]
print(needle in values_str) # True
False
True
True
True
boolとintの混在は特に誤判定の温床です。必要なら型を明示的に揃えてから比較してください。
NaNの扱いは特別です。math.isnan
やpandas.isna
など、目的に応じた関数で検出します。
大きなリストは線形探索
リストのinは線形探索(O(n))です。
サイズが大きい、または判定を大量に繰り返す場合は、セット(set)に変換して平均O(1)の探索にしましょう。
import time
N = 100_000
arr = list(range(N))
# 先頭/中央/末尾/非存在の探索時間を比較
for target in (0, N // 2, N - 1, -1):
t0 = time.perf_counter()
_ = target in arr
t1 = time.perf_counter()
print(f"list で {target=!r} を検索: {(t1 - t0)*1e6:.1f} µs (目安)")
# set に変換してから探索
s = set(arr)
for target in (0, N // 2, N - 1, -1):
t0 = time.perf_counter()
_ = target in s
t1 = time.perf_counter()
print(f"set で {target=!r} を検索: {(t1 - t0)*1e6:.1f} µs (目安)")
list で target=0 を検索: 0.5 µs (目安)
list で target=50000 を検索: 185.4 µs (目安)
list で target=99999 を検索: 362.6 µs (目安)
list で target=-1 を検索: 356.6 µs (目安)
set で target=0 を検索: 0.6 µs (目安)
set で target=50000 を検索: 0.4 µs (目安)
set で target=99999 を検索: 0.2 µs (目安)
set で target=-1 を検索: 0.9 µs (目安)
リストは位置によって時間が大きく変わるのに対し、セットは要素数にほぼ依存しない一定時間で判定できます。
ただし1回だけの判定なら、リストから都度set
に変換するコストが上回る場合もあります。繰り返しの判定や大規模データでこそ効果的です。
条件式を簡潔に書く
読みやすく、かつバグを生みにくい書き方を選びます。
user_role = "admin"
enabled_features = ["export", "share", "audit"]
# 良い例: in をそのまま条件に使い、候補はタプル/リストで明示
if user_role in ("admin", "owner") and "export" in enabled_features:
print("エクスポートを許可")
# 複数の必須権限をすべて満たすか(all)
required = ["read", "write"]
perms = ["read", "write", "delete"]
if all(p in perms for p in required):
print("必要な権限をすべて満たしています")
# いずれか1つあればよい(any)
if any(p in perms for p in ["approve", "write"]):
print("いずれかの権限を満たしています")
# 悪い例: 冗長な比較やネガティブ条件の多重は避ける
# if (user_role in ["admin", "owner"]) == True and not ("export" not in enabled_features):
# ...
エクスポートを許可
必要な権限をすべて満たしています
いずれかの権限を満たしています
if x in lst:
と簡潔に書くことで、意図が明確になります。
複数条件は、any
/all
や、場合によってはset
の集合演算(例: set(required) <= set(perms)
)を使うと表現力が上がります。
まとめ
本稿では、リストに対するin演算子の基本から、内部挙動、実用的な書き方、そして注意点とベストプラクティスまでを解説しました。
ポイントは次のとおりです。
inは要素の等値(==)で先頭から順に比較し、真偽値を返します。
型の違い(特にboolとint、NaN、Decimalなど)が結果に影響するため、必要に応じて型や表記を正規化しましょう。
大規模データや繰り返し判定ではsetを用いると劇的に高速になります。
条件式は簡潔に、any
/all
や集合演算を適切に組み合わせることで、読みやすく正確なコードにできます。
リストの中に特定の要素が含まれるかを判定する場面では、ここで紹介したパターンを組み合わせて堅牢な判定ロジックを構築してください。