閉じる

もう迷わない!Pythonでリストを昇順・降順にソートする

リストの並べ替えは、データ処理の第一歩です。

Pythonにはlist.sort()sorted()の2通りがあり、目的に応じて使い分けることで作業がぐっと楽になります。

本記事では、基礎から実践、つまずきポイントまでを丁寧に段階を追って解説します。

Pythonのリストをソートする基本(sort, sorted)

sortは破壊的(in-place)

list.sort()はリスト自身の順序を直接書き換える(破壊的)メソッドです

戻り値はNoneなので、代入しない点に注意します。

元のリストをそのまま並べ替えてよい場合に使います。

Python
# 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)は新しいリストを返す(非破壊)関数です

元のデータを温存したいときに有用です。

イテラブルならタプルや集合、辞書のキーなども並べ替えられます。

Python
# 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, reversekey, reverse
使い所元のリストを直接並べ替えたい元の順序を残したい、非リストを並べ替えたい

「元の順序を残す必要があるか」で選ぶのが基本方針です。

昇順ソートの基本(数値/文字列)

デフォルトは昇順です。

数値、文字列ともに自然な順序で並びます。

Python
# 数値の昇順
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()とは異なります。

Python
# 降順
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)です

等しいキーの要素は元の相対順序が保たれます

この性質は「段階的ソート」で威力を発揮します。

Python
# 先頭のカテゴリで並べ替え(同じカテゴリの中では元の順が保たれる)
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はセットで覚えると迷いません。

Python
# 基本形: 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']

文字列を長さでソート(key=len)

Python
# 長さで昇順
words = ["Python", "C", "JavaScript", "Go"]
print(sorted(words, key=len))
実行結果
['C', 'Go', 'Python', 'JavaScript']

絶対値でソート(key=abs)

Python
# 絶対値の小さい順
nums = [-10, 2, -3, 7, -1]
print(sorted(nums, key=abs))
実行結果
[-1, 2, -3, 7, -10]

大文字小文字を無視してソート(key=str.lower)

Python
# 大文字小文字を無視(小文字化した結果で比較)
words = ["banana", "Apple", "cherry", "apricot"]
print(sorted(words, key=str.lower))
実行結果
['Apple', 'apricot', 'banana', 'cherry']

ラムダ関数でカスタムキーを定義

Python
# 末尾の文字でソート
words = ["cat", "dog", "zebra", "ant"]
print(sorted(words, key=lambda s: s[-1]))
実行結果
['zebra', 'dog', 'cat', 'ant']

複雑なキーは関数を定義して再利用

可読性や再利用性を高めたい場合は、専用の関数を定義します。

Python
# 例: 文字列を「前後の空白除去→小文字化→長さ」を複合キーにする
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でタプルを返すと優先度順に比較されます。

Python
# クラス(昇順) → 点数(降順) → 名前(昇順) の優先順位で並べ替え
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}

operator.itemgetter/attrgetterで可読性UP

辞書やタプルにはitemgetter、オブジェクトやnamedtupleにはattrgetterが便利です。

Python
# 辞書の値を取り出す: 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)]
Python
# オブジェクト属性で並べ替え: 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')]

安定ソートを活かした段階的ソート

安定ソートなので、下位の条件から順にソートしていくと最終的に多段ソートが実現できます。

Python
# 下位条件から順に 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になります。

Python
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かどうか」を先頭に持つタプルを返すと安全に末尾へ送れます。

Python
# 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)のように調整します。

日本語やマルチバイト文字のソートの注意

PythonのデフォルトはUnicodeコードポイント順であり、五十音順辞書順と一致しない場合があります。

localeモジュールを使うと、OSのロケール依存ですがある程度自然な並びになります。

Python
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)でコピーしてから行います。

Python
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を使ってもデコレート・ソート・アンデコレートは自動で最適化されます。

とはいえ、重い処理は可能なら事前計算やキャッシュを検討しましょう。

Python
# ほぼ整列済みデータの例(最後に大きな値が少し)
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()」という基準で、迷わず確実に並べ替えを実装してください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!