Python の list.remove メソッド




== が True となる要素を1つ削除します。
要素がなければ ValueError を投げます。

list.remove(x)
リスト中で、値 x を持つ最初の要素を削除します。
該当する項目がなければエラーとなります。


# 1) list の remove メソッドは is か == が True となる
#    先頭の要素を削除します。
lst = [1, 2, 3, 1, 2, 3]
lst.remove(1)
lst  # [2, 3, 1, 2, 3]
# 2) 削除する要素がなければ ValueError を raise します。
lst = [1, 2, 3, 1, 2, 3]
lst.remove(4)  # ValueError


◯ 「値 x を持つ最初の要素を削除します。」って何?

最初「リスト中で、値 x を持つ最初の要素を削除します。」って何だ?と思っていました。例えば、11.0 は、それぞれ別の世界のオブジェクトです。1 は整数型 int class の世界で、1.0浮動小数点型 float class の世界です。

type(1)  # int
type(1.0)  # float


しかし、表現している ものは共に同じ 1 という値です。

1 == 1.0  # True


そのため remove メソッドにかけると削除されます。

lst = [1.0, 2.0, 3.0]
lst.remove(1)
lst  # [2.0, 3.0]

正確には...



is か == が True となる要素を1つ削除します。
要素がなければ ValueError を投げます。


表1.remove メソッドの動作
is == 削除
False False しない
False True する
True False する
True True する



is と == の動作はこちらに書きました。
Python の is と == の動作と違い - いっきに Python に詳しくなるサイト




◯ 動作確認

表1と同じ動作をするか確認しました。

# is ... False, == ... False
class A(object):
    # デフォルトで is が False なら  == も False
    # def __eq__(self, other):
    #     return self is other
    pass

a1 = A()
a2 = A()
a1 is a2  # False
a1 == a2  # False
lst_a = []
lst_a.append(a1)
lst_a.remove(a2)  # ValueError が起こる。
# is ... False, == ... True
class B(object):
    # __eq__ メソッドを定義すると == の動作を定義できる。
    def __eq__(self, other):
        return self is not other

b1 = B()
b2 = B()
b1 is b2  # False
b1 == b2  # True
lst_b = []
lst_b.append(b1)
lst_b.remove(b2)  # ValueError は起こらない。
# is ... True, == ... False
class C(object):
    # __eq__ メソッドを定義すると == の動作を定義できる。
    def __eq__(self, other):
        return self is not other

c = C()
c is c  # True
c == c  # False
lst_c = []
lst_c.append(c)
lst_c.remove(c)  # ValueError は起こらない。
# is ... True, == ... True
class D(object):
    # デフォルトで is が True なら  == も True
    # def __eq__(self, other):
    #     return self is other
    pass

d = D()
d is d  # True
d == d  # True
lst_d = []
lst_d.append(d)
lst_d.remove(d)  # ValueError は起こらない。


class C のように is が True の時に == が False になるクラスは、反射律が成り立たなくなるので、あまりよろしくなさそうですね。そんな実装をわざわざすることは、まず無いと思いますが。

等価性比較 (== および !=) のデフォルトの振る舞いは、オブジェクトの同一性に基づいています。(ワイの注記: object クラスを継承しているだけで __eq__ メソッドを定義していないクラスは、 is が True の時は == も True 、 is が False の時は == も False だよ。という意味)

従って、同一のインスタンスの等価性比較の結果は等しいとなり、同一でないインスタンスの等価性比較の結果は等しくないとなります。デフォルトの振る舞いをこのようにしたのは、全てのオブジェクトを反射的 (reflexive つまり x is y ならば x == y) なものにしたかったからです。

◯ CPython のコードを覗いてみた

is が True なら == が False でも消してしまうのはなんでなんやろうと思って実際に CPython を覗いてみました。

すると比較は PyObject_RichCompareBool で行われていました。この子は、比較するオブジェクトが同じなら 1 を返してくれるようです。

/*[clinic input]
list.remove
     value: object
     /
Remove first occurrence of value.
Raises ValueError if the value is not present.
[clinic start generated code]*/

static PyObject *
list_remove(PyListObject *self, PyObject *value)
/*[clinic end generated code: output=f087e1951a5e30d1 input=2dc2ba5bb2fb1f82]*/
{
    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {
        // ↓ここ
        int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ);
        if (cmp > 0) {
            if (list_ass_slice(self, i, i+1,
                               (PyObject *)NULL) == 0)
                Py_RETURN_NONE;
            return NULL;
        }
        else if (cmp < 0)
            return NULL;
    }
    PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
    return NULL;
}


int PyObject_RichCompareBool(PyObject o1, PyObject o2, int opid)
o1 と o2 を opid に指定した演算によって比較します。 opid は Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, または Py_GE, のいずれかでなければならず、それぞれ <, <=, ==, !=, >, および >= に対応します。比較結果が真ならば 1 を、偽ならば 0 を、エラーが発生すると -1 を返します。この関数は Python の式 o1 op o2 と同じで、 op が opid に対応する演算子です。

注釈 o1 と o2 が同一のオブジェクトである場合、 PyObject_RichCompareBool() は Py_EQ に対して常に 1 を返し、 Py_NE に対して常に 0 を返します。

おわりに

はじめて remove メソッドの説明をみた時に「値」を削除するって書かれてて、値ってなんだ?となって、 結局、なにを意味しているかわからなかったので、本当に長いこと使うことができませんでした。 CPython の中身をみないと挙動がわからないとか、なかなかいいトラップです。

Python の言語仕様というものは、確かに存在します、 しかし大抵の場合は、インタープリタの実装を書き起こしただけですし、 あるいは説明不足だったりします。
There is a language specification but in many cases it just codifies what the interpreter does or is even lacking.
私が見たい Python - The Python I Would Like To See


ところで「値」とはなんでしょうか?

list.remove(x)
リスト中で、値 x を持つ最初の要素を削除します。
該当する項目がなければエラーとなります。