Функциональность Python `in` vs.` __contains__`
Я реализовал метод __contains__
для класса в первый раз на днях, и поведение не было тем, что я ожидал. Я подозреваю, что есть какая-то тонкость в in
, который я не понимаю, и я надеялся, что кто-то сможет просветить меня.
Мне кажется, что оператор in
не просто переносит метод объекта __contains__
, но также пытается принудительно вывести вывод __contains__
в boolean. Например, рассмотрим класс
class Dummy(object):
def __contains__(self, val):
# Don't perform comparison, just return a list as
# an example.
return [False, False]
Оператор in
и прямой вызов метода __contains__
возвращают очень разные результаты:
>>> dum = Dummy()
>>> 7 in dum
True
>>> dum.__contains__(7)
[False, False]
Опять же, похоже, что in
вызывает __contains__
, но затем принуждает результат к bool
. Я не могу найти это поведение где-либо, кроме факта, что __contains__
документация говорит, что __contains__
должен возвращать только True
или False
.
Я счастлив, следуя конвенции, но может ли кто-нибудь сказать мне точную связь между in
и __contains__
?
Эпилог
Я решил выбрать ответ @eli-korvigo, но все должны смотреть на @ashwini-chaudhary comment о bug ниже.
Ответы
Ответ 1
Используйте источник, Люк!
Проследить реализацию оператора in
>>> import dis
>>> class test(object):
... def __contains__(self, other):
... return True
>>> def in_():
... return 1 in test()
>>> dis.dis(in_)
2 0 LOAD_CONST 1 (1)
3 LOAD_GLOBAL 0 (test)
6 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
9 COMPARE_OP 6 (in)
12 RETURN_VALUE
Как вы можете видеть, оператор in
становится командой COMPARE_OP
виртуальной машины. Вы можете найти это в ceval.c
TARGET(COMPARE_OP)
w = POP();
v = TOP();
x = cmp_outcome(oparg, v, w);
Py_DECREF(v);
Py_DECREF(w);
SET_TOP(x);
if (x == NULL) break;
PREDICT(POP_JUMP_IF_FALSE);
PREDICT(POP_JUMP_IF_TRUE);
DISPATCH();
Взгляните на один из переключателей в cmp_outcome()
case PyCmp_IN:
res = PySequence_Contains(w, v);
if (res < 0)
return NULL;
break;
Здесь мы имеем вызов PySequence_Contains
int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
Py_ssize_t result;
PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
if (sqm != NULL && sqm->sq_contains != NULL)
return (*sqm->sq_contains)(seq, ob);
result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
}
Это всегда возвращает int
(boolean).
P.S.
Спасибо Martijn Pieters за предоставление , чтобы найти реализацию оператора in
.
Ответ 2
В ссылка Python для __contains__
написано, что __contains__
должен возвращать True
или False
.
Если возвращаемое значение не является логическим, оно преобразуется в boolean. Вот доказательство:
class MyValue:
def __bool__(self):
print("__bool__ function runned")
return True
class Dummy:
def __contains__(self, val):
return MyValue()
Теперь напишите в оболочке:
>>> dum = Dummy()
>>> 7 in dum
__bool__ function runned
True
И bool()
непустого списка возвращает True
.
Edit:
Это только документация для __contains__
, если вы действительно хотите увидеть точное отношение, вы должны рассмотреть возможность поиска исходного кода, хотя я не уверен, где именно, но он уже ответил. В документации для сравнения написано:
Однако эти методы могут возвращать любое значение, поэтому, если оператор сравнения используется в булевом контексте (например, в условии оператора if
), Python будет вызывать bool(), чтобы определить, является ли результат истинным или ложным.
Итак, вы можете догадаться, что это похоже на __contains__
.