リストの並べ替えは、データ処理の第一歩です。
Pythonにはlist.sort()
とsorted()
の2通りがあり、目的に応じて使い分けることで作業がぐっと楽になります。
本記事では、基礎から実践、つまずきポイントまでを丁寧に段階を追って解説します。
- 関連記事:リスト(list)の使い方
Pythonのリストをソートする基本(sort, sorted)
sortは破壊的(in-place)
list.sort()
はリスト自身の順序を直接書き換える(破壊的)メソッドです。
戻り値はNoneなので、代入しない点に注意します。
元のリストをそのまま並べ替えてよい場合に使います。
# sort はリストをその場で並べ替えます(破壊的)
nums = [3, 1, 4, 1, 5]
print("before:", nums, "id:", id(nums))
ret = nums.sort() # 戻り値は None
print("after: ", nums, "id:", id(nums))
print("returned:", ret) # 確認のため
before: [3, 1, 4, 1, 5] id: 139...
after: [1, 1, 3, 4, 5] id: 139...
returned: None
同じオブジェクト(idが一致)のまま順序が変わったことが確認できます。
sortedは非破壊(新しいリストを返す)
sorted(iterable)
は新しいリストを返す(非破壊)関数です。
元のデータを温存したいときに有用です。
イテラブルならタプルや集合、辞書のキーなども並べ替えられます。
# sorted は新しいリストを返し、元のリストは変更しません(非破壊)
nums = [3, 1, 4, 1, 5]
sorted_nums = sorted(nums)
print("original:", nums, "id:", id(nums))
print("sorted: ", sorted_nums, "id:", id(sorted_nums))
original: [3, 1, 4, 1, 5] id: 139...
sorted: [1, 1, 3, 4, 5] id: 139...
idが異なるので、別オブジェクトが返っていることがわかります。
以下の表で違いをまとめます。
比較軸 | list.sort() | sorted() |
---|---|---|
破壊性 | あり(その場で変更) | なし(新しいリストを返す) |
戻り値 | None | 並べ替え済みの新しいリスト |
対象 | リストのみ | 任意のイテラブル |
主な引数 | key, reverse | key, reverse |
使い所 | 元のリストを直接並べ替えたい | 元の順序を残したい、非リストを並べ替えたい |
「元の順序を残す必要があるか」で選ぶのが基本方針です。
昇順ソートの基本(数値/文字列)
デフォルトは昇順です。
数値、文字列ともに自然な順序で並びます。
# 数値の昇順
nums = [10, -3, 7, 0, 5]
print(sorted(nums)) # 新しいリストで取得
# 文字列の昇順(辞書式順序、Unicodeコードポイント順)
words = ["banana", "Apple", "cherry"]
print(sorted(words))
[-3, 0, 5, 7, 10]
['Apple', 'banana', 'cherry']
文字列は大文字が小文字より先に来ることに注意します。
これはUnicode
のコードポイント順のためです。
降順にする(reverse=True)
reverse=True
を指定します。
これは比較を反転する指定であり、list.reverse()
とは異なります。
# 降順
nums = [10, -3, 7, 0, 5]
print(sorted(nums, reverse=True))
# 文字列を降順
words = ["banana", "Apple", "cherry"]
print(sorted(words, reverse=True))
[10, 7, 5, 0, -3]
['cherry', 'banana', 'Apple']
安定ソートの性質を理解する
Pythonのソートは安定ソート(Timsort)です。
等しいキーの要素は元の相対順序が保たれます。
この性質は「段階的ソート」で威力を発揮します。
# 先頭のカテゴリで並べ替え(同じカテゴリの中では元の順が保たれる)
items = [("A", 3), ("B", 2), ("A", 1), ("B", 1), ("A", 2)]
# 2番目の値で先にソートし、次に1番目の値でソート
items_sorted = sorted(items, key=lambda x: x[1]) # (A,3)が最後へ
items_sorted = sorted(items_sorted, key=lambda x: x[0]) # 安定性により同値の順序維持
print(items_sorted)
[('A', 1), ('A', 2), ('A', 3), ('B', 1), ('B', 2)]
keyとreverseの実践的な使い方
keyパラメータの基本(並べ替え基準)
key
は「並べ替えの基準を取り出す関数」です。
各要素に適用した結果で大小を決めます。
デフォルトは要素自身です。
keyとreverseはセットで覚えると迷いません。
# 基本形: sorted(iterable, key=関数, reverse=False)
people = ["bob", "Alice", "charlie"]
# 文字数(長さ)を基準に昇順
print(sorted(people, key=len))
# 文字数を基準に降順
print(sorted(people, key=len, reverse=True))
['bob', 'Alice', 'charlie']
['charlie', 'Alice', 'bob']
- 関連記事:ラムダ式の基本的な使い方
- 関連記事:関数の基本(def): 書き方と基本的な使い方
文字列を長さでソート(key=len)
# 長さで昇順
words = ["Python", "C", "JavaScript", "Go"]
print(sorted(words, key=len))
['C', 'Go', 'Python', 'JavaScript']
- 関連記事:文字列の長さを調べる
絶対値でソート(key=abs)
# 絶対値の小さい順
nums = [-10, 2, -3, 7, -1]
print(sorted(nums, key=abs))
[-1, 2, -3, 7, -10]
大文字小文字を無視してソート(key=str.lower)
# 大文字小文字を無視(小文字化した結果で比較)
words = ["banana", "Apple", "cherry", "apricot"]
print(sorted(words, key=str.lower))
['Apple', 'apricot', 'banana', 'cherry']
ラムダ関数でカスタムキーを定義
# 末尾の文字でソート
words = ["cat", "dog", "zebra", "ant"]
print(sorted(words, key=lambda s: s[-1]))
['zebra', 'dog', 'cat', 'ant']
複雑なキーは関数を定義して再利用
可読性や再利用性を高めたい場合は、専用の関数を定義します。
# 例: 文字列を「前後の空白除去→小文字化→長さ」を複合キーにする
def normalize_and_len(s: str):
s2 = s.strip().lower() # 前後の空白を除去して小文字化
return (len(s2), s2) # 長さ優先、同率は辞書順
data = [" Banana", "apple", "Cherry ", "apricot"]
print(sorted(data, key=normalize_and_len))
['apple', 'apricot', ' Banana', 'Cherry ']
タプルを返すkeyは「複数条件ソート」の基本形です。
複数条件でソートする(複数キー)
タプルを返すkeyで優先順位をつける
複数の基準を同時に扱う場合、keyでタプルを返すと優先度順に比較されます。
# クラス(昇順) → 点数(降順) → 名前(昇順) の優先順位で並べ替え
students = [
{"class": 2, "name": "Sato", "score": 82},
{"class": 1, "name": "Ito", "score": 82},
{"class": 1, "name": "Abe", "score": 95},
{"class": 2, "name": "Ueda", "score": 95},
{"class": 1, "name": "Ono", "score": 70},
]
sorted_students = sorted(
students,
key=lambda d: (d["class"], -d["score"], d["name"]) # 降順は符号反転で表現
)
for s in sorted_students:
print(s)
{'class': 1, 'name': 'Abe', 'score': 95}
{'class': 1, 'name': 'Ito', 'score': 82}
{'class': 1, 'name': 'Ono', 'score': 70}
{'class': 2, 'name': 'Ueda', 'score': 95}
{'class': 2, 'name': 'Sato', 'score': 82}
- 関連記事:タプル(tuple)の基本的な使い方
operator.itemgetter/attrgetterで可読性UP
辞書やタプルにはitemgetter
、オブジェクトやnamedtupleにはattrgetter
が便利です。
- operator.itemgetter – 関数形式の標準演算子 — Python 3.13.7 ドキュメント
- operator.attrgetter – 関数形式の標準演算子 — Python 3.13.7 ドキュメント
# 辞書の値を取り出す: itemgetter
from operator import itemgetter
students = [
{"class": 2, "name": "Sato", "score": 82},
{"class": 1, "name": "Ito", "score": 82},
{"class": 1, "name": "Abe", "score": 95},
]
# まず score 降順にしたいので -score を作る小さなヘルパを用意
def key_for_student(d):
return (d["class"], -d["score"], d["name"])
print(sorted(students, key=key_for_student))
# タプルであれば itemgetter(インデックス) も使えます
records = [(2, "Sato", 82), (1, "Ito", 82), (1, "Abe", 95)]
print(sorted(records, key=itemgetter(0, 2, 1))) # class → score → name
[{'class': 1, 'name': 'Abe', 'score': 95}, {'class': 1, 'name': 'Ito', 'score': 82}, {'class': 2, 'name': 'Sato', 'score': 82}]
[(1, 'Abe', 95), (1, 'Ito', 82), (2, 'Sato', 82)]
# オブジェクト属性で並べ替え: attrgetter
from operator import attrgetter
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
city: str
users = [User("Sato", 29, "Tokyo"), User("Abe", 35, "Nagoya"), User("Ito", 29, "Osaka")]
# 年齢(昇順)→名前(昇順)
print(sorted(users, key=attrgetter("age", "name")))
[User(name='Ito', age=29, city='Osaka'), User(name='Sato', age=29, city='Tokyo'), User(name='Abe', age=35, city='Nagoya')]
安定ソートを活かした段階的ソート
安定ソートなので、下位の条件から順にソートしていくと最終的に多段ソートが実現できます。
# 下位条件から順に sort する例(破壊的だが効率的)
students = [
{"class": 2, "name": "Sato", "score": 82},
{"class": 1, "name": "Ito", "score": 82},
{"class": 1, "name": "Abe", "score": 95},
{"class": 2, "name": "Ueda", "score": 95},
{"class": 1, "name": "Ono", "score": 70},
]
students.sort(key=lambda d: d["name"]) # 第3優先
students.sort(key=lambda d: -d["score"]) # 第2優先(降順)
students.sort(key=lambda d: d["class"]) # 第1優先
for s in students:
print(s)
{'class': 1, 'name': 'Abe', 'score': 95}
{'class': 1, 'name': 'Ito', 'score': 82}
{'class': 1, 'name': 'Ono', 'score': 70}
{'class': 2, 'name': 'Ueda', 'score': 95}
{'class': 2, 'name': 'Sato', 'score': 82}
初心者がつまずきやすいポイントと対処法
型が混在すると比較エラー(Python3)
Python3では異なる型同士の比較はできません。
混在したリストをそのままsorted()
するとTypeError
になります。
data = [1, "2", 3]
# print(sorted(data)) # TypeError: '<' not supported between instances of 'str' and 'int'
# 対処1: すべて文字列にそろえる
print(sorted(data, key=str))
# 対処2: すべて数値にそろえる(変換できない値が混じると別の例外になる点に注意)
data2 = ["10", "2", "30"]
print(sorted(data2, key=int))
[1, '2', 3]
['2', '10', '30']
比較する前に「同じ型」に正規化するのが基本です。
Noneを含む場合の回避策(keyで末尾へ)
Noneは数値や文字列と比較できません。
key
で「Noneかどうか」を先頭に持つタプルを返すと安全に末尾へ送れます。
# None を最後に送る昇順: (Noneかどうか, 値 or 番兵)
data = [3, None, 1, None, 2]
safe_sorted = sorted(data, key=lambda x: (x is None, x if x is not None else float("inf")))
print(safe_sorted)
# 文字列版の例
data_s = ["b", None, "a"]
safe_sorted_s = sorted(data_s, key=lambda x: (x is None, x if x is not None else ""))
print(safe_sorted_s)
[1, 2, 3, None, None]
['', 'a', 'b'] # None は末尾(表示のため '' を番兵にしています)
番兵値は並べ替え方向に合わせて選びます。
降順でNoneを最後にしたいときは(x is None, -x)
のように調整します。
- 関連記事:Noneの意味と使い方
日本語やマルチバイト文字のソートの注意
PythonのデフォルトはUnicodeコードポイント順であり、五十音順や辞書順と一致しない場合があります。
locale
モジュールを使うと、OSのロケール依存ですがある程度自然な並びになります。
import locale
words = ["がく", "か゛く", "かく", "あい", "アイ"]
try:
locale.setlocale(locale.LC_COLLATE, "ja_JP.UTF-8") # 環境により失敗する場合あり
print(sorted(words, key=locale.strxfrm))
except locale.Error:
# フォールバック: ロケールが使えない環境ではそのまま
print(sorted(words))
['アイ', 'あい', 'かく', 'がく', 'か゛く'] # 出力は環境に依存
ロケールは環境依存で結果が変わる可能性があります。
厳密な日本語ソートが必要な場合は外部ライブラリ(Python-ICUなど)の検討が現実的です。
元の並びを残したい場合はsortedを使う
原則として「元の並びを残したいならsorted()
」です。
どうしてもsort()
を使いたい場合は、l.copy()
やlist(l)
でコピーしてから行います。
data = [3, 1, 2]
# 非破壊
asc = sorted(data)
print("original:", data, "sorted:", asc)
# 破壊的にしたくない場合のコピー + sort
copy_data = data.copy()
copy_data.sort(reverse=True)
print("original:", data, "desc(copy):", copy_data)
original: [3, 1, 2] sorted: [1, 2, 3]
original: [3, 1, 2] desc(copy): [3, 2, 1]
パフォーマンスの基本(Timsortと部分的な整列)
Pythonの並べ替えはTimsortで、平均・最悪はO(n log n)
ですが、既に部分的に整列しているデータに非常に強いのが特長です。
例えば「すでにほぼ昇順」や「ランが長い」データでは高速です。
また、key関数は要素ごとに1回だけ呼ばれるため、高コストなkeyを使ってもデコレート・ソート・アンデコレートは自動で最適化されます。
とはいえ、重い処理は可能なら事前計算やキャッシュを検討しましょう。
# ほぼ整列済みデータの例(最後に大きな値が少し)
data = list(range(100000)) + [100001, 100002, 100003]
# 実行時間の測定は環境依存のため、ここではアルゴリズムの特性だけ示します
sorted_data = sorted(data) # Timsort により効率よく並び替えられる
print(len(sorted_data), sorted_data[:3], sorted_data[-3:])
100003 [0, 1, 2] [100001, 100002, 100003]
まとめ
list.sort()は破壊的、sorted()は非破壊という違いをまず押さえ、keyとreverseを自在に使いこなすことで、昇順・降順はもちろん複数条件の高度な並べ替えまで対応できます。
Pythonの安定ソートは段階的ソートに強力で、部分整列に強いTimsortの特性も相まって実務に十分な性能を発揮します。
データの型やNone混入、日本語のソートなどの落とし穴は、key
による正規化やlocale
の活用で回避できます。
今日からは「元を残すならsorted()
、その場で良ければsort()
」という基準で、迷わず確実に並べ替えを実装してください。