Pythonのsetは「重複しない要素の集まり(順序なし・変更できる)」、tupleは「順序つきで変更できない並び」です。 どちらも丸や波のかっこで書くので似て見えますが、役割はまったく別です。setは「含まれているか」「共通するものは何か」を高速に調べるための型、tupleは「変わらない値の組」を1つにまとめるための型です。

この記事では、list・tuple・set・frozenset・dictの5種類を1枚の比較表にまとめたうえで、集合演算の一覧、setにlistを入れたときのエラーとその直し方、要素1つのtuple (1,) の書き方、in 判定の速度差の計測までを扱います。コードはすべてPython 3.14.7で実行し、出力をそのまま載せています(3.11.15でも実行し、差があった箇所は注記しています)。

setとtupleの違いの早見表

まずは5種類のコレクションを並べた比較表です。迷ったらこの表の「向いている用途」の列を見てください。

項目 list tuple set frozenset dict
書き方 [1, 2] (1, 2) {1, 2} frozenset({1, 2}) {"a": 1}
空の書き方 [] () set() frozenset() {}
順序 あり あり なし なし あり(挿入順)
重複 持てる 持てる 持てない 持てない キーは持てない
変更(追加・削除) できる できない できる できない できる
添字 x[0] 使える 使える 使えない 使えない キーで引く
ハッシュ可能(setの要素やdictのキーにできる) できない できる(中身もハッシュ可能なとき) できない できる できない
in の計算量(平均) O(n) O(n) O(1) O(1) O(1)(キー)
集合演算 | & - ^ なし なし あり あり キーのビューにあり
向いている用途 順番に並べて増減させる 変わらない値の組、戻り値、キー 重複を除く、所属の判定、共通部分 定数の集合、setの中に入れる集合 キーから値を引く

setとtupleだけを取り出すと、次の3点が大きな違いです。

観点 set tuple
順序と重複 順序なし・重複なし 順序あり・重複あり
変更 add remove で変更できる 作ったあとは変更できない
得意なこと in 判定と集合演算が速い 添字・アンパック・辞書のキー

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python setとtupleの違い|集合の使い方とlist・dict比較表」

setとtupleをまず動かして違いを見る

最初に、いちばん小さなコードで両者の性質を確かめます。次のコードを basic.py として保存し、python3 basic.py で実行します。

# basic.py(Python 3.14.7で実行)
tags = {"python", "sql", "python", "git"}   # set:重複は1つにまとまる
print(tags, len(tags))

point = (35.68, 139.76)                      # tuple:順序つきの組
print(point[0], point)

print(type({}), type(set()), type(()), type((1)), type((1,)))

実行結果:

{'git', 'python', 'sql'} 3
35.68 (35.68, 139.76)
<class 'dict'> <class 'set'> <class 'tuple'> <class 'int'> <class 'tuple'>

ここから読み取れることは3つあります。

  • setは重複を自動で取り除きます。 "python" を2回書いても要素数は3です。
  • setの表示順は書いた順とは限りません。 上の出力はアルファベット順に見えますが、たまたまです。同じコードを3.11.15で実行すると {'sql', 'git', 'python'} と表示されました。文字列のハッシュ値は実行のたびに変わるため、順番を当てにしない書き方にします。
  • {} は空のdictで、空のsetは set() です。 また (1) はただの整数で、要素1つのtupleは (1,) と書きます(後の章で詳しく扱います)。

set(集合)の使い方

setを作る

やりたいこと 書き方 結果
値を並べて作る {1, 2, 3} {1, 2, 3}
空のsetを作る set() set()
listから作る(重複を除く) set([1, 1, 2]) {1, 2}
文字列から作る set("hello") 文字ごとの集合
内包表記で作る {len(w) for w in words} 長さの集合

内包表記の例です。

print({len(w) for w in ["apple", "kiwi", "melon", "fig"]})
{3, 4, 5}

"apple" と "melon" はどちらも5文字なので、5は1つにまとまります。

要素を追加・削除する

s = {1, 2, 3}
s.add(4)            # 1つ追加
s.update([5, 6])    # まとめて追加(listやtupleを渡せる)
s.discard(99)       # 無くてもエラーにならない
try:
    s.remove(99)    # 無いとKeyError
except KeyError as e:
    print("KeyError:", e)
s.remove(1)
print(s)
print(s.pop(), s)   # どれか1つを取り出して削除

実行結果:

KeyError: 99
{2, 3, 4, 5, 6}
2 {3, 4, 5, 6}
メソッド 動き 要素が無いとき
add(x) 1つ追加 ―
update(iterable) まとめて追加 ―
remove(x) 削除 KeyError
discard(x) 削除 何も起きない
pop() どれか1つを取り出す 空なら KeyError
clear() 全部削除 ―

「あれば消す」なら discard、「必ずあるはず」を確かめたいなら remove と使い分けると、意図がコードに表れます。pop() はどの要素が出てくるか決まっていないので、「順番に取り出す」用途にはlistや collections.deque が向いています。

重複を取り除く(順序を保つ方法も)

setのいちばんよく使われる場面が重複の除去です。ただし、set() に通すと元の順序は保たれません。 順序を保ちたいときは dict.fromkeys() を使います。

items = ["b", "a", "c", "a", "b", "d"]
print(set(items))                    # 順序は保証されない
print(list(dict.fromkeys(items)))    # 最初に出た順を保つ
print(sorted(set(items)))            # 並べ替えてよいならこれ

実行結果:

{'a', 'c', 'd', 'b'}
['b', 'a', 'c', 'd']
['a', 'b', 'c', 'd']
やりたいこと 書き方
重複を除く(順序は問わない) set(items)
重複を除く(出現順を保つ) list(dict.fromkeys(items))
重複を除いて並べ替える sorted(set(items))
重複があるか調べる len(items) != len(set(items))

dictはPython 3.7以降、挿入順を保つことが言語仕様になっています。dict.fromkeys(items) は「キーだけのdict」を作るので、重複を除きつつ順序が残ります。

集合演算の一覧(| & - ^ とメソッド版)

setの本領は集合演算です。2つの名簿を例にします。

A = {"田中", "佐藤", "鈴木", "高橋"}   # 月曜の参加者
B = {"鈴木", "高橋", "伊藤"}           # 火曜の参加者

for name, r in [("A | B", A | B), ("A & B", A & B),
                ("A - B", A - B), ("B - A", B - A), ("A ^ B", A ^ B)]:
    print(name, "=>", sorted(r))

実行結果(見やすいように sorted() で並べ替えています):

A | B => ['伊藤', '佐藤', '田中', '鈴木', '高橋']
A & B => ['鈴木', '高橋']
A - B => ['佐藤', '田中']
B - A => ['伊藤']
A ^ B => ['伊藤', '佐藤', '田中']

演算子とメソッドの対応表

意味 演算子 メソッド版 自分自身を書き換える版 上の例での結果
和集合(どちらかにいる) A | B A.union(B) A |= B / A.update(B) 5人
積集合(両方にいる) A & B A.intersection(B) A &= B / A.intersection_update(B) 鈴木・高橋
差集合(Aだけにいる) A - B A.difference(B) A -= B / A.difference_update(B) 佐藤・田中
対称差(片方だけにいる) A ^ B A.symmetric_difference(B) A ^= B / A.symmetric_difference_update(B) 伊藤・佐藤・田中

差集合だけは左右を入れ替えると結果が変わります。 A - B は「月曜だけの人」、B - A は「火曜だけの人」です。

包含関係を調べる(issubset など)

意味 演算子 メソッド版
部分集合か(AはBに全部含まれる) A <= B A.issubset(B)
真部分集合か(含まれていて、かつ等しくない) A < B ―
上位集合か(AはBを全部含む) A >= B A.issuperset(B)
共通要素が1つもないか ― A.isdisjoint(B)
同じ集合か A == B ―
print({"鈴木"} <= A, {"鈴木"}.issubset(A), A.issuperset({"鈴木"}),
      A >= {"鈴木"}, A.isdisjoint({"山田"}),
      {"鈴木"} < {"鈴木"}, {"鈴木"} <= {"鈴木"})
True True True True True False True

最後の2つに注目してください。< は「等しい場合はFalse」、<= は「等しい場合もTrue」 です。「必要な権限をすべて持っているか」のような判定は required <= owned(または required.issubset(owned))で1行に書けます。

演算子とメソッドの違い:メソッドはlistも受け取れる

演算子版は両辺がset(またはfrozenset)である必要があります。メソッド版は、listやtupleなど反復できるものなら何でも受け取ります。

print(sorted(A.union(["山田"])))   # メソッドはlistを渡せる
try:
    A | ["山田"]                    # 演算子はsetどうしのみ
except TypeError as e:
    print("TypeError:", e)
['佐藤', '山田', '田中', '鈴木', '高橋']
TypeError: unsupported operand type(s) for |: 'set' and 'list'
書き方 相手がlistのとき 向いている場面
A | B などの演算子 TypeError 両方がsetだとわかっているとき。短く読みやすい
A.union(B) などのメソッド そのまま動く 相手がlistやジェネレータのとき。3つ以上もまとめて A.union(B, C) と書ける

setにlistを入れるとTypeError、tupleなら入る

setを使い始めて最初に出会いやすいエラーがこれです。

try:
    {[1, 2], [3, 4]}          # listをsetの要素にする
except TypeError as e:
    print("TypeError:", e)

print({(1, 2), (3, 4), (1, 2)})   # tupleなら入る(重複も除かれる)

Python 3.14.7での実行結果:

TypeError: cannot use 'list' as a set element (unhashable type: 'list')
{(1, 2), (3, 4)}

Python 3.11.15では、同じコードのエラー文は次のとおりでした。

TypeError: unhashable type: 'list'

3.14ではメッセージが「setの要素として使えない」と具体的になっています。 検索するときは unhashable type: 'list' の部分が共通なので、どちらの版でも同じ原因です。

なぜlistは入らないのか(ハッシュ可能とは)

setは内部でハッシュテーブルという仕組みを使っています。要素から「ハッシュ値」という整数を計算し、その値をもとに置き場所を決めるので、in 判定が一瞬で終わります。

この仕組みが成り立つには、要素のハッシュ値が後から変わらないことが必要です。listは中身を書き換えられるので、ハッシュ値を決められません。これを「ハッシュ可能ではない(unhashable)」と呼びます。tupleは変更できないので、ハッシュ値を計算できます。

型 ハッシュ可能か setの要素・dictのキーにできるか
int float str bool None できる できる
tuple(中身もハッシュ可能) できる できる
frozenset できる できる
list できない できない
set できない できない
dict できない できない

実行して確かめた結果です(3.11.15の出力。hash() を直接呼んだ場合は3.14でも同じ文面です)。

TypeError: unhashable type: 'list'
TypeError: unhashable type: 'set'
TypeError: unhashable type: 'dict'

tupleの中にlistがあると、やはり入らない

「tupleなら入る」には条件があります。tupleの中身もすべてハッシュ可能である必要があります。

try:
    {(1, [2, 3])}             # tupleの中にlist
except TypeError as e:
    print("TypeError:", e)
TypeError: cannot use 'tuple' as a set element (unhashable type: 'list')

setの中にsetを入れたいときはfrozenset

setはsetの要素にできませんが、frozenset(変更できないset)なら入ります。

try:
    {{1, 2}}
except TypeError as e:
    print("TypeError:", e)

print({frozenset({1, 2}), frozenset({2, 1})})
TypeError: cannot use 'set' as a set element (unhashable type: 'set')
{frozenset({1, 2})}

frozenset({1, 2}) と frozenset({2, 1}) は同じ集合なので、1つにまとまっています。「順序を問わない組」を重複なく持ちたいとき(例:2人組のペアを、どちらが先でも同じとみなす)に役立ちます。

dictのキーでも同じルール

dictのキーもハッシュ可能である必要があるので、同じことが起こります。

try:
    d = {[1, 2]: "x"}
except TypeError as e:
    print("TypeError:", e)

d = {(1, 2): "x", frozenset({"a"}): "y"}
print(d)
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
{(1, 2): 'x', frozenset({'a'}): 'y'}

直し方の早見表:

エラーになる書き方 直した書き方 考え方
{[1, 2], [3, 4]} {(1, 2), (3, 4)} listをtupleに
set(list_of_lists) {tuple(x) for x in list_of_lists} 内側をtupleに変換してから
{{1, 2}} {frozenset({1, 2})} 内側のsetをfrozensetに
d[[x, y]] = v d[(x, y)] = v キーをtupleに
{(1, [2, 3])} {(1, (2, 3))} 中のlistもtupleに

tupleの使い方と、要素1つの (1,) の罠

tupleは「変更できない並び」

tp = (1, 2, 3)
try:
    tp[0] = 9
except TypeError as e:
    print("TypeError:", e)
try:
    tp.append(4)
except AttributeError as e:
    print("AttributeError:", e)
TypeError: 'tuple' object does not support item assignment
AttributeError: 'tuple' object has no attribute 'append'

代入も追加もできません。「この値の組は途中で変わらない」とコードで示せるのがtupleの価値です。

ただし、変更できないのは「tupleが指している先」だけです。中にlistが入っていれば、そのlistの中身は変えられます。

tp2 = (1, [2, 3])
tp2[1].append(4)
print(tp2)
(1, [2, 3, 4])

このようなtupleは前の章で見たとおりハッシュ可能ではないので、setの要素やdictのキーにはできません。

要素1つのtupleはカンマが必要

tupleを作っているのは丸かっこではなく、カンマです。 要素が1つのときにカンマを忘れると、ただの値になります。

a = ("admin")     # ただの文字列
b = ("admin",)    # 要素1つのtuple
print(type(a), type(b))

for x in a: print(x, end=" ")
print()
for x in b: print(x, end=" ")
print()

t = 1,            # かっこが無くてもカンマがあればtuple
print(t)

実行結果:

<class 'str'> <class 'tuple'>
a d m i n 
admin 
(1,)

("admin") をforで回すと、文字列が1文字ずつ出てきます。エラーにならずに動いてしまうので、気づきにくいところです。

書き方 型 補足
() tuple 空のtuple
(1) int かっこは計算の優先順位を表すだけ
(1,) tuple 要素1つのtuple
1, tuple かっこなしでも作れる
("admin") str 文字列のまま
("admin",) tuple 要素1つのtuple
tuple(["admin"]) tuple listから作る

この罠は逆向きにも起こります。行末に余分なカンマを付けると、意図せずtupleになります。

name = "admin",    # 行末のカンマに注意 → ("admin",) になる

設定値を書き写したときなどに起こりやすいので、値を表示して ('admin',) のようにかっことカンマが付いていたら、行末のカンマを確認します。

アンパックと複数の戻り値

tupleがもっとも自然に使われるのがアンパック(複数の変数への一括代入)です。

x, y = 1, 2
x, y = y, x                  # 入れ替え
print(x, y)

first, *rest = (1, 2, 3, 4)  # 先頭と残り
print(first, rest)

def minmax(v):
    return min(v), max(v)    # tupleを返している

lo, hi = minmax([3, 1, 4])
print(lo, hi, minmax([3, 1, 4]))
2 1
1 [2, 3, 4]
1 4 (1, 4)

return min(v), max(v) は、カンマで区切った時点でtupleになっています。関数から複数の値を返す仕組みは、実はtupleを1つ返しているだけです。*rest で受けた側はlistになる点も覚えておくと便利です。

tupleをdictのキーにする

2つ以上の値の組み合わせで値を引きたいとき、tupleをキーにすると素直に書けます。

stock = {("A棟", 1): 12, ("A棟", 2): 5, ("B棟", 1): 0}
print(stock[("A棟", 2)])   # 5
print(("B棟", 2) in stock)  # False

文字列を連結して "A棟-2" のようなキーを作る方法もありますが、tupleなら区切り文字の扱いを考えずに済み、あとで分解するのも簡単です。

frozensetの使い方

frozensetは「変更できないset」です。setとの関係は、listとtupleの関係に似ています。

fs = frozenset({1, 2})
try:
    fs.add(3)
except AttributeError as e:
    print("AttributeError:", e)

print(fs | {3}, type(fs | {3}), type({3} | fs))
AttributeError: 'frozenset' object has no attribute 'add'
frozenset({1, 2, 3}) <class 'frozenset'> <class 'set'>
項目 set frozenset
add remove update 使える 使えない
集合演算 | & - ^ 使える 使える(新しい集合を返す)
in 判定 O(1) O(1)
setの要素・dictのキー できない できる
リテラル {1, 2} なし(frozenset(...) で作る)

setとfrozensetを混ぜて演算すると、結果の型は左側の型になります。 上の出力で fs | {3} はfrozenset、{3} | fs はsetになっています。

frozensetが向いている場面:

  • モジュールの定数として持つ集合(例:ALLOWED_EXT = frozenset({"png", "jpg", "gif"}))。うっかり add される心配がなくなります。
  • setの中に集合を入れたいとき、または集合をdictのキーにしたいとき。
  • 関数の既定値にしたいとき。変更できないので、既定値を共有しても影響が広がりません。

in判定の速度差を計測する

比較表の「in の計算量」を、実際に測って確かめます。要素数を100・1万・100万と変え、いちばん見つかりにくい末尾の要素を探す時間を測りました。

# speed.py(Python 3.14.7で実行)
import timeit

for N in (100, 10_000, 1_000_000):
    data = list(range(N))
    target = N - 1                       # 末尾の要素を探す
    cs = {"list": data, "tuple": tuple(data), "set": set(data),
          "frozenset": frozenset(data), "dict": dict.fromkeys(data)}
    num = 200 if N >= 1_000_000 else 20000
    row = []
    for k, c in cs.items():
        tm = min(timeit.repeat(lambda: target in c, number=num, repeat=3)) / num
        row.append(f"{k}={tm*1e6:.3f}us")
    print(N, " ".join(row))

実行結果(1回あたりの時間。us はマイクロ秒=100万分の1秒):

100 list=2.236us tuple=2.161us set=0.028us frozenset=0.028us dict=0.028us
10000 list=281.215us tuple=242.377us set=0.038us frozenset=0.037us dict=0.039us
1000000 list=19856.027us tuple=14585.661us set=0.036us frozenset=0.035us dict=0.038us

表にまとめます。

要素数 list tuple set frozenset dict
100 2.2 us 2.2 us 0.028 us 0.028 us 0.028 us
10,000 281 us 242 us 0.038 us 0.037 us 0.039 us
1,000,000 19,856 us 14,586 us 0.036 us 0.035 us 0.038 us
  • listとtupleは、要素数に比例して時間が延びています(O(n))。先頭から1つずつ比べていくためです。
  • set・frozenset・dictは、要素数が1万倍になってもほぼ同じ時間です(O(1))。ハッシュ値から置き場所を直接求めるためです。
  • listとtupleの in に大きな差はありません。 「tupleだから速い」わけではなく、探し方が同じだからです。

数値は共有マシンで測ったもので、環境や混み具合によって変わります(同じコードを3.11.15で測ったときは、100万件のlistで約5,000〜17,000 usの幅がありました)。絶対値よりも「listは件数に比例し、setは一定」という傾きの違いを見てください。

ループの中のinをsetに変える

速度差がもっとも効くのは、ループの中で in を繰り返すときです。2万件のIDどうしの共通部分を求める例で比べます。

import timeit

ids_a = list(range(0, 20000))
ids_b = list(range(10000, 30000))

# 直す前:listに対してinを2万回
t1 = timeit.timeit(lambda: [i for i in ids_a if i in ids_b], number=1)

# 直した後1:片方をsetにしてからin
sb = set(ids_b)
t2 = timeit.timeit(lambda: [i for i in ids_a if i in sb], number=1)

# 直した後2:集合演算で一度に求める
t3 = timeit.timeit(lambda: set(ids_a) & set(ids_b), number=1)

print(f"list-in={t1*1000:.1f}ms set-in={t2*1000:.2f}ms set&={t3*1000:.2f}ms")

実行結果(Python 3.14.7):

list-in=4167.5ms set-in=0.42ms set&=1.36ms
書き方 時間 特徴
[i for i in ids_a if i in ids_b](listのまま) 約4,168 ms 2万×最大2万回の比較
[i for i in ids_a if i in sb](片方をset化) 約0.42 ms 元の順序を保てる
set(ids_a) & set(ids_b) 約1.36 ms 1行で書ける。順序は保たれない

sb = set(ids_b) の1行を足すだけで、約4秒が1ミリ秒未満になりました。 順序を保ちたいなら2番目、順序を問わないなら3番目の書き方が向いています。

メモリ使用量も確認する

速さと引き換えに、setとdictはメモリを多めに使います。要素1,000個のコンテナ本体のサイズ(sys.getsizeof、単位はバイト)を測りました。

list tuple set frozenset dict
8,056 8,048 32,984 32,984 36,952

setはlistの約4倍です。数十件を1回だけ調べる程度ならlistやtupleのままで十分で、件数が多い、または in を何度も繰り返すときにsetへ変える、という判断がちょうどよい目安です。

どれを選ぶかの判断表

やりたいこと 選ぶ型 理由
順番に並べて、追加・削除・並べ替えをしたい list 順序つきで変更できる
変わらない値の組を持ちたい(座標、年月日、範囲) tuple 変更できないことを示せる
関数から複数の値を返したい tuple return a, b と書ける
dictのキーに複数の値の組を使いたい tuple ハッシュ可能
重複を取り除きたい set 自動で1つにまとまる
含まれているかを何度も調べたい set in がO(1)
2つの集まりの共通・差分を求めたい set & - で1行
変更されない定数の集合を持ちたい frozenset add できない
setの中に集合を入れたい frozenset ハッシュ可能
キーから値を引きたい dict キーの in もO(1)
出現回数を数えたい collections.Counter dictの仲間。数える処理が1行

判断の流れを図にすると次のようになります。

flowchart TD
    A[値の集まりを持ちたい] --> B{キーから値を引く?}
    B -- はい --> D[dict]
    B -- いいえ --> C{順序や重複に意味がある?}
    C -- ある --> E{あとで変更する?}
    E -- する --> F[list]
    E -- しない --> G[tuple]
    C -- ない --> H{あとで変更する?}
    H -- する --> I[set]
    H -- しない --> J[frozenset]

namedtuple・dataclassとの関係

tupleは便利ですが、row[0] row[1] のような添字アクセスは、何番目が何の値かを覚えておく必要があります。項目に名前を付けたいときの選択肢が、namedtupleとdataclassです。

from collections import namedtuple
from dataclasses import dataclass

Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
print(p, p.x, p[0], p == (1, 2), {p, Point(1, 2)})

@dataclass
class P1:
    x: int
    y: int

@dataclass(frozen=True)
class P2:
    x: int
    y: int

try:
    {P1(1, 2)}                      # 通常のdataclassはsetに入らない
except TypeError as e:
    print("TypeError:", e)

print({P2(1, 2), P2(1, 2)})         # frozen=Trueなら入る
try:
    P2(1, 2).x = 5
except Exception as e:
    print(type(e).__name__ + ":", e)

実行結果(Python 3.14.7):

Point(x=1, y=2) 1 1 True {Point(x=1, y=2)}
TypeError: cannot use 'P1' as a set element (unhashable type: 'P1')
{P2(x=1, y=2)}
FrozenInstanceError: cannot assign to field 'x'
項目 tuple namedtuple dataclass dataclass(frozen=True)
名前でアクセス(p.x) できない できる できる できる
添字でアクセス(p[0]) できる できる できない できない
変更 できない できない できる できない
setの要素・dictのキー できる できる できない できる
(1, 2) と == で等しい ― 等しい 等しくない 等しくない
型ヒント・既定値・メソッド ― 一部 書きやすい 書きやすい
  • namedtupleは「名前つきのtuple」 です。tupleの性質(変更できない、ハッシュ可能、アンパックできる)をそのまま引き継ぎます。
  • dataclassは通常のクラスです。== を自動で定義する代わりにハッシュは無効になるため、そのままではsetに入りません。frozen=True を付けると変更できなくなり、setの要素やdictのキーに使えます。

「setに入れたい」「dictのキーにしたい」という要件があるなら、tuple・namedtuple・dataclass(frozen=True) のいずれかを選ぶ、と覚えておくと迷いません。namedtupleやCounterなどの詳細は「Python collectionsの使い方」、dataclassの既定値やslotsは「Python dataclassの使い方」、listとdictのメソッドは「Python リスト 操作 一覧」「Python辞書(dict)の使い方」の各記事で扱っています。

つまずきやすいポイントのチェックリスト

症状 原因 直し方
TypeError: unhashable type: 'list' setの要素やdictのキーにlistを使った tupleに変換する
TypeError: 'set' object is not subscriptable s[0] と添字でアクセスした list(s)[0] か sorted(s)[0]、または next(iter(s))
空のsetのつもりが dict になる {} と書いた set() と書く
forで1文字ずつ出てくる ("admin") とカンマなしで書いた ("admin",) と書く
値が ('admin',) になる 行末に余分なカンマがある カンマを消す
setの順序が実行のたびに変わる setは順序を持たない sorted(s) で並べる、順序が要るならlistやdictを使う
A | [1, 2] で TypeError 演算子はsetどうしのみ A.union([1, 2]) か A | set([1, 2])
{1, 1.0, True} が {1} になる 1 == 1.0 == True でハッシュ値も同じ 型を区別したいなら (type(x), x) のtupleで持つ
tupleの中のlistが変わっている tupleは「参照先」を固定するだけ 中身もtupleにする

最後から2番目の行は意外に感じるかもしれません。実行すると次のようになります。

print(1 in {1.0, True}, {1, 1.0, True}, {0, False, 0.0})
True {1} {0}

Pythonでは 1 == 1.0 == True が成り立ち、ハッシュ値も同じなので、setの中では同じ要素として扱われます。最初に入れた値が残ります。

よくある質問

Q. setとtupleはどちらが速いですか? A. 用途によります。in で含まれるかを調べるならsetが圧倒的に速く、今回の計測では100万件でtupleが約14,586マイクロ秒、setが約0.036マイクロ秒でした。一方、先頭から順に取り出すだけならtupleで十分で、メモリもsetの約4分の1で済みます。「探す」ならset、「並べて持つ」ならtupleと考えてください。

Q. listとtupleはどう使い分ければよいですか? A. あとで追加・削除・並べ替えをするならlist、作ったあと変えない値の組ならtupleです。tupleにするとdictのキーやsetの要素に使えるようになり、「この値は変わらない」という意図も伝わります。迷ったら「同じ種類のものを並べる=list」「違う役割の値を組にする=tuple」と考えると選びやすくなります。

Q. setの要素を順番どおりに取り出すにはどうしますか? A. setは順序を持たないので、取り出すときに sorted(s) で並べ替えます。入れた順を保ちたい場合は、setの代わりに dict.fromkeys(items) でキーだけのdictを作ると、重複を除きつつ挿入順が残ります。

Q. frozensetはどんなときに使いますか? A. 「変更されない集合」が必要なときです。モジュールの定数にする、setの中に集合を入れる、集合をdictのキーにする、関数の既定値にする、といった場面に向いています。集合演算や in の速さはsetと同じです。

まとめ

  • setは「順序なし・重複なし・変更できる」、tupleは「順序あり・重複あり・変更できない」。 役割が違うので、用途で選びます。
  • in 判定はset・frozenset・dictがO(1)、list・tupleがO(n)。 100万件の計測で数十万倍の差が出ました。
  • 集合演算は | & - ^、メソッド版は union intersection difference symmetric_difference。 メソッド版はlistも受け取れます。
  • setの要素とdictのキーはハッシュ可能なものだけ。 listはtupleに、setはfrozensetに変えると入ります。
  • 要素1つのtupleは (1,)。 tupleを作るのはかっこではなくカンマです。
  • 空のsetは set()。 {} は空のdictです。
  • 名前つきで持ちたいならnamedtupleか dataclass(frozen=True)。 どちらもsetの要素にできます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python setとtupleの違い|集合の使い方とlist・dict比較表」

株式会社RJCでは、Pythonのsetやtupleのようなデータ構造の選び方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?