¿Cómo se implementa el método __contains__ de la clase de lista en Python?

Supongamos que defino las siguientes variables:

mode = "access" allowed_modes = ["access", "read", "write"] 

Actualmente tengo una statement de comprobación de tipo que es

 assert any(mode == allowed_mode for allowed_mode in allowed_modes) 

Sin embargo, parece que puedo reemplazar esto simplemente con

 assert mode in allowed_modes 

De acuerdo con la respuesta de ThiefMaster en Python List Class __contains__ Method Funcionalidad , estos dos deben ser equivalentes. ¿Es este el caso? ¿Y cómo podría verificar esto fácilmente buscando el código fuente de Python?

No, no son equivalentes. Por ejemplo:

 >>> mode = float('nan') >>> allowed_modes = [mode] >>> any(mode == allowed_mode for allowed_mode in allowed_modes) False >>> mode in allowed_modes True 

Consulte Operaciones de prueba de membresía para obtener más detalles, incluida esta statement:

Para tipos de contenedores como list, tuple, set, frozenset, dict o collections.deque, la expresión x in y es equivalente a any(x is e or x == e for e in y) .

Las listas de Python están definidas en código C.

Puede verificarlo mirando el código en el repository :

 static int list_contains(PyListObject *a, PyObject *el) { Py_ssize_t i; int cmp; for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i) cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i), Py_EQ); return cmp; } 

Es bastante sencillo ver que este código recorre los elementos de la lista y se detiene cuando la primera comparación de igualdad ( Py_EQ ) entre el y PyList_GET_ITEM(a, i) devuelve 1.

No es equivalente ya que cualquiera requiere una llamada de función extra, una expresión de generador y cosas.

 >>> mode = "access" >>> allowed_modes =["access", "read", "write"] >>> >>> def f1(): ... mode in allowed_modes ... >>> def f2(): ... any(mode == x for x in allowed_modes) ... >>> >>> >>> import dis >>> dis.dis dis.dis( dis.disassemble( dis.disco( dis.distb( >>> dis.dis(f1) 2 0 LOAD_GLOBAL 0 (mode) 3 LOAD_GLOBAL 1 (allowed_modes) 6 COMPARE_OP 6 (in) 9 POP_TOP 10 LOAD_CONST 0 (None) 13 RETURN_VALUE >>> dis.dis(f2) 2 0 LOAD_GLOBAL 0 (any) 3 LOAD_CONST 1 ( at 0x7fb24a957540, file "", line 2>) 6 LOAD_CONST 2 ('f2..') 9 MAKE_FUNCTION 0 12 LOAD_GLOBAL 1 (allowed_modes) 15 GET_ITER 16 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 POP_TOP 23 LOAD_CONST 0 (None) 26 RETURN_VALUE >>> 

Esto es más instructivo que la fuente de Python para los métodos en sí, pero aquí está la fuente de __contains__ para las listas y el bucle está en C, que probablemente sea más rápido que un bucle de Python.

Algunos números de tiempo confirman esto.

 >>> import timeit >>> timeit.timeit(f1) 0.18974408798385412 >>> timeit.timeit(f2) 0.7702703149989247 >>>