Pythonのlist.__mul__
ごさいようじょです。
まずは感じてください。
これでわかった、とか、知ってるわ、という方は少なくともごさいようじょよりPythonができます。
いま
a = [[]] * 3
で
[[], [], []]
を作ります。
Pythonicではないとかそういうのは後にしてください。
女児にそういうのを求めるのは野暮というもので、ところで、野暮って言葉の意味知ってますか?
先頭要素の空リストに何かをappendします。
a[0].append(1)
純粋な心を持っているので、ここで
[[1], [], []]
を期待します。
しかし上にあるように実際a
は
[[1], [1], [1]]
になります。
つまり
[~] * n
で返ってくる
[~, ~, ~]
の~
の各々は同じ実体、というわけです。
蛇足です。
さもありなん。
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に書かれることを推奨いたします。