Python listのsort()の挙動には注意が必要

はじめに

リストのsort()メソッドを使ったときに,Pythonの値渡しと参照渡しについて調べることになったのでまとめる.

以下の3つに注意が必要.

Pythonにおいて,

  • sort()は破壊的メソッドである
  • list型は変更可能な型(mutable)である
  • mutableなオブジェクトは参照渡しをする

sortは破壊的メソッドである

「破壊的メソッド」という単語は,PythonというよりはRuby業界の用語の気がするが,わかりやすので使わせてもらう.

破壊的メソッドは,そのメソッドが対象とするオブジェクトの値そのものを変更してしまうようなメソッドの事.

In [1]:
hoge = [3,1,5,2,9,8,6,4,7]

hoge.sort()

print(hoge)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

元のリストオブジェクトの値そのものが変更される.

list型は変更可能な型(Mutable)である

まず,mutableとその対をなすimmutableという概念について説明する.オブジェクトの種類を表す概念であり,「list型はmutaleなオブジェクトである」というような文脈で用いられる.

簡単に言えば,

  • mutaleなオブジェクト:値を変更できるオブジェクト(list, dict, set等)
  • immutableなオブジェクト:値を変更できないオブジェクト(int, float, str等)

である.

定数という概念がないPythonにおいて,値を変更できないオブジェクトなんて存在するのかと思うかもしれない.

ここで言う「値を変更できない」とは,生成時に確保されたメモリ領域が保持する値を後から変更できないという意味である.

実際に見てみるのがはやい.

In [2]:
#int型はimmutableなオブジェクトである

#int型のオブジェクトを生成
hoge_1 = int(5)

#idでメモリ領域のアドレスを取得
print("First check:", id(hoge_1))

#見かけ上の値の変更を試みる
hoge_1 = int(10)
print("Second check:", id(hoge_1))
First check: 1901771344
Second check: 1901771504

このように見かけ上は値が置き換わっているが,実際には新しい領域を確保している.つまり最初のメモリ領域の値は変更できない.

In [3]:
#list型はmutableなオブジェクトである

#list型のオブジェクトを生成
hoge_list = [1,2,3,4,5]

#idでメモリ領域のアドレスを取得
print("First check:", id(hoge_list))

#値の変更を試みる
hoge_list[4] = 7
print("Second check:", id(hoge_list))
First check: 868397590728
Second check: 868397590728

メモリ領域のアドレスは変わらないので,値の変更に成功している.

mutableなオブジェクトは参照渡しをする

実は,immutableなオブジェクトも関数や変数に値を渡す場合は参照渡しをしている.

ただ,その後に変数の値が変更されるタイミングでメモリ領域を新しく確保するので,見かけ上値渡しに見える.

In [4]:
hoge_1 = int(4)

print("First check:", id(hoge_1))

hoge_2 = hoge_1

print("Second check:", id(hoge_2))

hoge_2 = int(6)

print("Third check:", id(hoge_2))
First check: 1901771312
Second check: 1901771312
Third check: 1901771376

一方,mutableなオブジェクトであるlistの場合は以下のようになる.

In [5]:
hoge_list_1 = [1, 3, 5]

print("First check:", id(hoge_list_1))

hoge_list_2 = hoge_list_1

print("Second check:", id(hoge_list_2))

hoge_list_2[0] = 7

print("Third check:", id(hoge_list_2))

#値そのものを確認
print("----------")
print("hoge_list_1:", hoge_list_1)
print("hoge_list_2:", hoge_list_2)
First check: 868411306888
Second check: 868411306888
Third check: 868411306888
----------
hoge_list_1: [7, 3, 5]
hoge_list_2: [7, 3, 5]

このように,hoge_list_2の値を変更するとhoge_list_1の値も変更される.

つまり

なにが言いたかったかというと以下のような挙動が起こりますよってこと.

In [6]:
x = [4, 2, 9, 5, 1, 8, 3, 7, 6]

y = x

z = y

z.sort()

print("x:",x)
print("y:",y)
print("z:",z)
x: [1, 2, 3, 4, 5, 6, 7, 8, 9]
y: [1, 2, 3, 4, 5, 6, 7, 8, 9]
z: [1, 2, 3, 4, 5, 6, 7, 8, 9]

説明は不要かと思われる.

こんなこともあるって頭に入れておく必要があるな.

2