Pythonでリストの順序を整える場面は、数値の集計結果を並べたり、文字列の一覧を見やすくしたりと日常的にあります。
本記事では、昇順・降順の基本から、reverseやkeyによる柔軟な並べ替え、初心者がつまずきやすい注意点、効率よく書くコツまで順を追って丁寧に解説します。
リストを昇順・降順にソートする基本
sortメソッドとsorted関数の違い
リストの並べ替えには、インスタンスメソッドのlist.sort()
と組み込み関数のsorted()
の2通りがあります。
まずは両者の性質を押さえておくと、その後の選択が迷いにくくなります。
ざっくり比較表
項目 | list.sort() | sorted() |
---|---|---|
呼び出し方 | my_list.sort(...) | sorted(iterable, ...) |
破壊的か | はい(元のリストを書き換える) | いいえ(新しいリストを返す) |
戻り値 | None | ソート済みの新しいリスト |
対象 | リストのみ | 任意の反復可能オブジェクト(リスト、タプル、集合、ジェネレータなど) |
メモリ使用 | 少なめ(原則インプレース) | 追加メモリが必要 |
主な用途 | 大きなリストを効率よく並べ替える | 元のデータを残したい、一時的に並べ替えたい |
動作の違いをコードで確認
sort()
は戻り値がNone
である点に注意します。
一方sorted()
は新しいリストを返し、元のリストは変化しません。
# sort() と sorted() の違いを確認するサンプル
nums = [3, 1, 2]
# インプレースに並べ替え(元のリストが書き換わる)
result_in_place = nums.sort()
print("nums after sort():", nums) # 並び替え済み
print("return value of sort():", result_in_place) # None に注意
# 新しいリストを受け取る(sortedは元のリストを変更しない)
nums2 = [3, 1, 2]
result_new = sorted(nums2)
print("nums2 after sorted():", nums2) # 変更なし
print("result of sorted():", result_new) # 並び替え済みの新しいリスト
nums after sort(): [1, 2, 3]
return value of sort(): None
nums2 after sorted(): [3, 1, 2]
result of sorted(): [1, 2, 3]
昇順にソートする書き方
昇順(小さい順)は最も基本的な並べ替えです。
デフォルト設定のままで実現できます。
list.sort()で昇順
# 昇順にソート(インプレース)
numbers = [5, 1, 9, 3, 3]
numbers.sort() # デフォルトは昇順
print(numbers)
[1, 3, 3, 5, 9]
sorted()で昇順
# 昇順にソート(新しいリストを受け取る)
numbers = [5, 1, 9, 3, 3]
sorted_numbers = sorted(numbers)
print("original:", numbers)
print("sorted :", sorted_numbers)
original: [5, 1, 9, 3, 3]
sorted : [1, 3, 3, 5, 9]
降順にソートする書き方
降順(大きい順)はreverse=True
を指定します。
# 降順にソートする2通り
arr1 = [5, 1, 9, 3]
desc1 = sorted(arr1, reverse=True) # 新しいリスト
print(desc1)
arr2 = [5, 1, 9, 3]
arr2.sort(reverse=True) # インプレース
print(arr2)
[9, 5, 3, 1]
[9, 5, 3, 1]
補足として、数値であればkey=lambda x: -x
のように負号を使う方法もありますが、reverse=True
のほうが意図が明確で、NaN
などの特殊値や非数値の並べ替えでは安全性が高いです。
reverseとkeyで柔軟にソート
reverse=Trueで降順にする
reverse=True
は「降順に並べ替える」ための引数です。
よく混同されるlist.reverse()
は「現在の並び順を単純にひっくり返す」だけで、値の大小には基づきません。
両者は用途が異なります。
# reverse=True と list.reverse() の違い
a = [3, 1, 2]
a.reverse() # 元の順序を単に反転
print("list.reverse() ->", a) # [2, 1, 3] (降順ではない)
b = [3, 1, 2]
b.sort(reverse=True) # 大小に基づき降順に並べ替え
print("sort(reverse=True) ->", b) # [3, 2, 1]
list.reverse() -> [2, 1, 3]
sort(reverse=True) -> [3, 2, 1]
keyで並べ替え基準を指定
key
引数に「各要素から比較用の値を取り出す関数」を渡すと、柔軟に並べ替え基準を指定できます。
key
関数は各要素に1回ずつ呼ばれ、その戻り値に基づいて整列されます。
文字列を長さで並べ替える
# 文字列の長さで昇順にソート
words = ["banana", "kiwi", "apple", "fig"]
by_length = sorted(words, key=len) # len をそのまま key に使える
print(by_length)
['fig', 'kiwi', 'apple', 'banana']
タプルの2番目→3番目の値で並べ替える
# (名前, 身長, 体重) のタプルを、身長→体重の順に昇順ソート
students = [
("A", 180, 70),
("B", 170, 80),
("C", 180, 65),
]
ordered = sorted(students, key=lambda x: (x[1], x[2]))
print(ordered)
[('B', 170, 80), ('C', 180, 65), ('A', 180, 70)]
辞書の特定キーで並べ替える
# リスト内の辞書を、年齢→名前の順で昇順に並べ替え
users = [
{"name": "alice", "age": 30},
{"name": "bob", "age": 25},
{"name": "carol", "age": 30},
]
ordered = sorted(users, key=lambda u: (u["age"], u["name"]))
print(ordered)
[{'name': 'bob', 'age': 25}, {'name': 'alice', 'age': 30}, {'name': 'carol', 'age': 30}]
文字列を大文字小文字無視でソート
大文字小文字を区別せずにソートしたいときはkey=str.lower
が簡単です。
より国際化に配慮するならstr.casefold
が推奨です。
# 大文字小文字を無視してソートする例
names = ["bob", "Alice", "carol", "ALAN", "apple", "Apple", "APPLE"]
print("default :", sorted(names)) # デフォルト(大文字が先)
print("lower :", sorted(names, key=str.lower)) # 大小無視
print("casefold:", sorted(names, key=str.casefold)) # より厳密な大小無視
# 同じキー(例: 'apple')同士は元の順序が保たれる(安定ソート)
pair = ["apple", "Apple", "APPLE"]
print("stable :", sorted(pair, key=str.lower))
default : ['ALAN', 'APPLE', 'Alice', 'Apple', 'apple', 'bob', 'carol]
lower : ['ALAN', 'Alice', 'apple', 'Apple', 'APPLE', 'bob', 'carol']
casefold: ['ALAN', 'Alice', 'apple', 'Apple', 'APPLE', 'bob', 'carol']
stable : ['apple', 'Apple', 'APPLE']
初心者がつまずくポイント
元のリストを残したいならsorted
list.sort()
は元のリストを直接書き換えるため、後で元の順序を使いたい場合に困ります。
そういうときはsorted()
を使って新しいリストを受け取ります。
コピーしてからsort()
でも構いません。
# 元のリストを残したい場合の2通り
data = [7, 2, 5]
# 1) sorted() を使う
sorted_data = sorted(data)
print("data :", data)
print("sorted_data:", sorted_data)
# 2) コピーしてから sort()
copy_data = data[:] # あるいは list(data) や data.copy()
copy_data.sort()
print("copy_data :", copy_data)
data : [7, 2, 5]
sorted_data: [2, 5, 7]
copy_data : [2, 5, 7]
なお、sorted_list = my_list.sort()
としてしまうとsorted_list
はNone
になってしまいます。
この書き方は避けましょう。
型が混在するリストはソートできない
Python 3では、数値と文字列のように比較不可能な型が混在しているとTypeError
になります。
対策は「型をそろえる」か「key
で比較用の同型値を作る」ことです。
# 型が混在しているとエラーになる例
mixed = [1, "2", 3]
try:
print(sorted(mixed))
except TypeError as e:
print("TypeError:", e)
# 対策1: すべて数値に変換(文字列が数値として解釈できる前提)
numeric_sorted = sorted(int(x) for x in mixed)
print("numeric_sorted:", numeric_sorted)
# 対策2: keyで同じ型に正規化して比較(ここでは文字列化)
mixed2 = [10, "2", 3, "11"]
as_string_sorted = sorted(mixed2, key=lambda x: str(x))
print("as_string_sorted:", as_string_sorted)
# Noneが混じる場合は「Noneを最後に送る」キーにする
with_none = [3, None, 1, None]
none_last = sorted(with_none, key=lambda x: (x is None, x))
print("none_last:", none_last)
TypeError: '<' not supported between instances of 'str' and 'int'
numeric_sorted: [1, 2, 3]
as_string_sorted: [10, 11, 2, 3]
none_last: [1, 3, None, None]
上の「Noneを最後に送る」テクニックは、キーをタプル(x is None, x)
にすることで、最初の要素(False/True)で「Noneかどうか」を判定し、Trueを持つ要素(=None)が後ろに回る仕組みです。
両方がNoneのときのみ2番目の値x
が比較されますが、同じNone同士なので問題になりません。
安定ソートで同一キーの順序維持
Pythonのソートは安定ソートです。
これは「比較キーが同じ要素同士は元の相対順を維持する」という性質を意味します。
複数条件で整列するときにとても役立ちます。
# 2番目の値(グループ)でソート。グループ内の元の順序は維持される。
records = [
("A", 2),
("B", 1),
("C", 1),
("D", 3),
]
ordered = sorted(records, key=lambda x: x[1])
print(ordered) # ('B', 1) と ('C', 1) の順序は元のまま
[('B', 1), ('C', 1), ('A', 2), ('D', 3)]
Pythonリストソートのベストプラクティス
大きなリストを効率よくソート
Pythonのソート実装はTimsortと呼ばれるアルゴリズムで、平均・最悪計算量はおおむねO(n log n)です。
ほぼ整列済みのデータでは非常に高速に動作します。
大きなリストでは、以下の点に気を付けると効率的です。
- 元のリストを破壊して構わないなら
list.sort()
を使い、追加メモリ消費を抑えます。 - 高価な変換を伴う基準で並べ替えるなら、
key
で一度だけ計算させます(Pythonのソートは「キーを各要素1回だけ」計算してから並べ替えます)。 - 上位N件だけ欲しい場合は、全部をソートせず
heapq.nlargest
やheapq.nsmallest
を使います。
# key関数は各要素に1回だけ呼ばれる(重い処理ならここで済む)
counter = {"n": 0}
def key_func(x):
counter["n"] += 1
return x % 10 # 例: 下1桁で並べ替える
data = list(range(20, 0, -1)) # 20..1
_ = sorted(data, key=key_func)
print("key called:", counter["n"], "times")
key called: 20 times
# 全件ソートせずに上位N件を求める
import heapq
data = [5, 1, 9, 3, 7, 4]
top3 = heapq.nlargest(3, data) # 大きい順に3件
low3 = heapq.nsmallest(3, data) # 小さい順に3件
print("top3:", top3)
print("low3:", low3)
top3: [9, 7, 5]
low3: [1, 3, 4]
可読性が高い書き方のコツ
読みやすさを意識したkey
の書き方は、後から見返したときの理解速度に直結します。
短いlambda
で済む場合はそれで良いですが、条件が増えるなら関数名を付けたキー関数に切り出すと意図が伝わりやすくなります。
# 長さ→アルファベット順(大小無視)の順に並べる、という意図を関数名に込める
def by_len_then_alpha(s: str):
return (len(s), s.lower())
words = ["pear", "fig", "banana", "Apple", "apricot"]
print(sorted(words, key=by_len_then_alpha))
['fig', 'pear', 'Apple', 'banana', 'apricot']
辞書やタプルの特定要素で並べ替えるなら、operator
モジュールのitemgetter
/attrgetter
が簡潔で意図が明快です。
# itemgetterで辞書のキーを指定して並べ替える
from operator import itemgetter
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Carol", "age": 25},
]
print(sorted(users, key=itemgetter("age", "name")))
[{'name': 'Bob', 'age': 25}, {'name': 'Carol', 'age': 25}, {'name': 'Alice', 'age': 30}]
また、「降順にしたいからキーに負号を付ける」といったトリックは避け、reverse=True
を使うと読み手に優しいコードになります。
複数条件のソートは、タプルキー(第1条件, 第2条件, ...)
にまとめると1回で済み、保守も容易です。
sortとsortedの使い分けまとめ
使い分けの基準をまとめると次のようになります。
- 元のリストを書き換えて良い、追加メモリを抑えたい、大量データで効率を重視:
list.sort()
- 元の順序を残したい、イテラブルをソートして新しいリストが欲しい、一時的に並べ替えたい:
sorted()
- 降順は
reverse=True
、基準変更はkey=...
を基本形として覚える
まとめ
Pythonのリストソートは、list.sort()
とsorted()
の違いを理解し、reverse
とkey
を組み合わせて使いこなすだけで、非常に幅広い要件に対応できます。
基本の昇順・降順から始め、文字列の大小無視、複数条件の並べ替え、Noneや型の混在への対処などを段階的に押さえておくと、実務での困りごとを確実に解決できます。
大きなリストではインプレースなsort()
やheapq
の活用で効率に配慮し、可読性の高いkey
の書き方を心がけることで、保守しやすいコードへとつながります。
慣れてくれば、ソートは「迷わず書ける定番テクニック」になりますので、ぜひ手を動かして身につけていきましょう。