Pythonのlist.__mul__

ごさいようじょです。

まずは感じてください。

wandbox.org

これでわかった、とか、知ってるわ、という方は少なくともごさいようじょよりPythonができます。

いま

a = [[]] * 3

[[], [], []]

を作ります。
Pythonicではないとかそういうのは後にしてください。
女児にそういうのを求めるのは野暮というもので、ところで、野暮って言葉の意味知ってますか?

先頭要素の空リストに何かをappendします。

a[0].append(1)

純粋な心を持っているので、ここで

[[1], [], []]

を期待します。

しかし上にあるように実際a

[[1], [1], [1]]

になります。

つまり

[~] * n

で返ってくる

[~, ~, ~]

~の各々は同じ実体、というわけです。

蛇足です。

wandbox.org

さもありなん。

Pythonの「基本は参照」という原則からすると、まあわかりますが気を付けてないとふとした時に踏み抜きます。
(損失は概ね1時間でした)

ここからはもっと蛇足です。

さて、list.__mul__はmethod_wrapperです。
実装は多分これです。

cpython/listobject.c at 3.6 · python/cpython · GitHub

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;github
    if (size == 0)
        return PyList_New(0);

    np = (PyListObject *) PyList_New(size);
    if (np == NULL)
        return NULL;

    items = np->ob_item;
    if (Py_SIZE(a) == 1) {
        elem = a->ob_item[0];
        for (i = 0; i < n; i++) {
            items[i] = elem;
            Py_INCREF(elem);
        }
        return (PyObject *) np;
    }
    p = np->ob_item;
    items = a->ob_item;
    for (i = 0; i < n; i++) {
        for (j = 0; j < Py_SIZE(a); j++) {
            *p = items[j];
            Py_INCREF(*p);
            p++;
        }
    }
    return (PyObject *) np;
}

(これって行番号つかないんですか?)

528, 537行目からが実際のコピー処理です。
elem, pはそれぞれ格納先の各要素(PyObject**), for中のelemが元要素(上の対応では~)です。

ポインタを渡しているので、参照です。(雑理論)

またPy_INCREFでelemの参照カウンタをインクリメントしているのでここからもコピーっぽくないのが感じられます。(雑推論)

つまりそういうことなので、データサイエンティストの皆様におかれましてはPythonのコードはPythonicに書かれることを推奨いたします。