Vielleicht noch zur Erklärung: Das range() erstellt ja seit Python 3.x keine Liste mehr, sondern ein spezielles range-Objekt. Dieses kennt sein Minimum und Maximum. Für Ganzzahlen (PyLong) ist der Code so optimiert, dass ein ``x in range(N)`` einem ``min(range(N)) <= x <= max(range(N))`` entspricht. Ein Set würde dieses Verhalten quasi kaputt machen. Die beiden performanteren Vorschläge übertragen den "Min-Max-Trick" nochmals auf die zu testenden Zahlen. Der Overhead für das Set entfällt dabei komplett. Daher kommt die entsprechend bessere Laufzeit zustande. An sich ist halt die Frage, warum man dafür überhaupt ein range-Objekt benutzt, wo man doch einfach direkt einen Vergleich mit dem Minimum und Maximum machen könnte...
Auszug aus rangeobject.c:
Code: Alles auswählen
/* Assumes (PyLong_CheckExact(ob) || PyBool_Check(ob)) */
static int
range_contains_long(rangeobject *r, PyObject *ob)
{
PyObject *zero = _PyLong_GetZero(); // borrowed reference
int cmp1, cmp2, cmp3;
PyObject *tmp1 = NULL;
PyObject *tmp2 = NULL;
int result = -1;
/* Check if the value can possibly be in the range. */
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
if (cmp1 == -1)
goto end;
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
}
else { /* negative steps: stop < ob <= start */
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
}
/* ... */
}
Genau genommen ist dem range-Objekt nicht das MInimum und Maximum bekannt, sondern lediglich die Werte für start und stop. Durch die Verzweigung auf Grundlage des step-Arguments läuft es aber darauf hinaus.