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]


Python における値って何?

正確には...



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


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



is と == の動作はこちらに書きました。




◯ 動作確認

表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 を返します。