>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
여러 요소가있는 튜플에서도 작동하며 두 버전 모두 선형으로 성장하는 것 같습니다.
>>> timeit.timeit("'x' in ('x', 'y')")
0.04866674801541748
>>> timeit.timeit("'x' == 'x' or 'x' == 'y'")
0.06565782838087131
>>> timeit.timeit("'x' in ('y', 'x')")
0.08975995576448526
>>> timeit.timeit("'x' == 'y' or 'x' == 'y'")
0.12992391047427532
이것을 바탕으로, 나는 대신 모든 곳에서 완전히 사용하기 시작 해야한다고 생각합니다 !in
==
답변
데이비드 울 레버 (David Wolever)에 대해 언급했듯이, 눈에 보이는 것보다 더 많은 것이 있습니다. 두 가지 방법 모두에 전달 is
; 당신은 이것을함으로써 이것을 증명할 수 있습니다
min(Timer("x == x", setup="x = 'a' * 1000000").repeat(10, 10000))
#>>> 0.00045456900261342525
min(Timer("x == y", setup="x = 'a' * 1000000; y = 'a' * 1000000").repeat(10, 10000))
#>>> 0.5256857610074803
첫 번째는 신원으로 확인하기 때문에 너무 빠를 수 있습니다.
하나가 다른 것보다 더 오래 걸리는 이유를 알아 보려면 실행을 추적하십시오.
바이트 코드가 관련 ceval.c
되어 COMPARE_OP
있기 때문에 둘 다에서 시작합니다.
TARGET(COMPARE_OP) {
PyObject *right = POP();
PyObject *left = TOP();
PyObject *res = cmp_outcome(oparg, left, right);
Py_DECREF(left);
Py_DECREF(right);
SET_TOP(res);
if (res == NULL)
goto error;
PREDICT(POP_JUMP_IF_FALSE);
PREDICT(POP_JUMP_IF_TRUE);
DISPATCH();
}
스택에서 값을 팝합니다 (기술적으로는 하나만 팝)
PyObject *right = POP();
PyObject *left = TOP();
그리고 비교를 실행합니다 :
PyObject *res = cmp_outcome(oparg, left, right);
cmp_outcome
이것입니다 :
static PyObject *
cmp_outcome(int op, PyObject *v, PyObject *w)
{
int res = 0;
switch (op) {
case PyCmp_IS: ...
case PyCmp_IS_NOT: ...
case PyCmp_IN:
res = PySequence_Contains(w, v);
if (res < 0)
return NULL;
break;
case PyCmp_NOT_IN: ...
case PyCmp_EXC_MATCH: ...
default:
return PyObject_RichCompare(v, w, op);
}
v = res ? Py_True : Py_False;
Py_INCREF(v);
return v;
}
이것은 경로가 분할되는 곳입니다. PyCmp_IN
분기 않습니다
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);
}
튜플은 다음과 같이 정의됩니다.
static PySequenceMethods tuple_as_sequence = {
...
(objobjproc)tuplecontains, /* sq_contains */
};
PyTypeObject PyTuple_Type = {
...
&tuple_as_sequence, /* tp_as_sequence */
...
};
그래서 지점
if (sqm != NULL && sqm->sq_contains != NULL)
촬영되고 *sqm->sq_contains
함수 인 (objobjproc)tuplecontains
, 촬영한다.
이것은 않습니다
static int
tuplecontains(PyTupleObject *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, PyTuple_GET_ITEM(a, i),
Py_EQ);
return cmp;
}
… 잠깐, PyObject_RichCompareBool
다른 지점에서 취한 것이 아니 었 습니까? 아니, 그이었다 PyObject_RichCompare
.
그 코드 경로는 짧았 기 때문에이 두 가지 속도로 떨어질 것입니다. 비교하자.
int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{
PyObject *res;
int ok;
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
if (v == w) {
if (op == Py_EQ)
return 1;
else if (op == Py_NE)
return 0;
}
...
}
코드 경로는 PyObject_RichCompareBool
거의 즉시 종료됩니다. 의 경우 PyObject_RichCompare
, 그렇습니다
PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
PyObject *res;
assert(Py_LT <= op && op <= Py_GE);
if (v == NULL || w == NULL) { ... }
if (Py_EnterRecursiveCall(" in comparison"))
return NULL;
res = do_richcompare(v, w, op);
Py_LeaveRecursiveCall();
return res;
}
Py_EnterRecursiveCall
/ Py_LeaveRecursiveCall
콤보는 이전 경로에서 촬영되지 않지만, 이들은 상대적으로 빠른 매크로되는거야 증가 및 일부 전역을 감소시키는 후 단락.
do_richcompare
않습니다 :
static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
richcmpfunc f;
PyObject *res;
int checked_reverse_op = 0;
if (v->ob_type != w->ob_type && ...) { ... }
if ((f = v->ob_type->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
...
}
...
}
이 호출에 몇 가지 빠른 검사를 수행 v->ob_type->tp_richcompare
하다
PyTypeObject PyUnicode_Type = {
...
PyUnicode_RichCompare, /* tp_richcompare */
...
};
어떤
PyObject *
PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)
{
int result;
PyObject *v;
if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
Py_RETURN_NOTIMPLEMENTED;
if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)
return NULL;
if (left == right) {
switch (op) {
case Py_EQ:
case Py_LE:
case Py_GE:
/* a string is equal to itself */
v = Py_True;
break;
case Py_NE:
case Py_LT:
case Py_GT:
v = Py_False;
break;
default:
...
}
}
else if (...) { ... }
else { ...}
Py_INCREF(v);
return v;
}
즉,이 바로 가기는 left == right
…하지만 수행 한 후에 만
if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)
모든 경로에서 모두 다음과 같이 보입니다 (수동으로 재귀 적으로 알려진 분기를 인라인하고 풀고 정리합니다)
POP() # Stack stuff
TOP() #
#
case PyCmp_IN: # Dispatch on operation
#
sqm != NULL # Dispatch to builtin op
sqm->sq_contains != NULL #
*sqm->sq_contains #
#
cmp == 0 # Do comparison in loop
i < Py_SIZE(a) #
v == w #
op == Py_EQ #
++i #
cmp == 0 #
#
res < 0 # Convert to Python-space
res ? Py_True : Py_False #
Py_INCREF(v) #
#
Py_DECREF(left) # Stack stuff
Py_DECREF(right) #
SET_TOP(res) #
res == NULL #
DISPATCH() #
vs
POP() # Stack stuff
TOP() #
#
default: # Dispatch on operation
#
Py_LT <= op # Checking operation
op <= Py_GE #
v == NULL #
w == NULL #
Py_EnterRecursiveCall(...) # Recursive check
#
v->ob_type != w->ob_type # More operation checks
f = v->ob_type->tp_richcompare # Dispatch to builtin op
f != NULL #
#
!PyUnicode_Check(left) # ...More checks
!PyUnicode_Check(right)) #
PyUnicode_READY(left) == -1 #
PyUnicode_READY(right) == -1 #
left == right # Finally, doing comparison
case Py_EQ: # Immediately short circuit
Py_INCREF(v); #
#
res != Py_NotImplemented #
#
Py_LeaveRecursiveCall() # Recursive check
#
Py_DECREF(left) # Stack stuff
Py_DECREF(right) #
SET_TOP(res) #
res == NULL #
DISPATCH() #
자, PyUnicode_Check
그리고 PyUnicode_READY
그들은 단지 필드의 몇 가지를 확인 꽤 저렴하지만 상단의 하나가 작은 코드 경로입니다 분명해야한다, 그것은 적은 함수 호출, 하나의 스위치 문이 단지 비트 얇다.
TL; DR :
둘 다 if (left_pointer == right_pointer)
; 차이점은 그들이 그곳에 가기 위해 얼마나 많은 일을하는지입니다. in
그냥 덜합니다.
답변
이 놀라운 행동을 만들어내는 세 가지 요소가 있습니다.
첫째 : in
연산자는 단축키 ( x is y
)를 확인하기 전에 등식 ( x == y
)을 확인합니다 .
>>> n = float('nan')
>>> n in (n, )
True
>>> n == n
False
>>> n is n
True
둘째, 파이썬의 문자열 인턴 때문에, 모두 "x"
의의는 "x" in ("x", )
동일합니다 :
>>> "x" is "x"
True
(큰 경고 :이 구현 고유의 동작입니다! is
해야 결코 그것 때문에 문자열을 비교하는 데 사용되지 않습니다 때때로 놀라운 답변을 제공, 예를 들어 "x" * 100 is "x" * 100 ==> False
)
셋째 :에 설명 된대로 Veedrac의 환상적인 대답 , tuple.__contains__
( x in (y, )
이다 대략 에 동등한 (y, ).__contains__(x)
보다 빠른 신원 확인을 수행하는 지점에 도달) str.__eq__
(다시, x == y
이다 대략 에 해당 x.__eq__(y)
하지 않습니다).
x in (y, )
논리적으로 동등한 것보다 상당히 느리기 때문에 이에 대한 증거를 볼 수 있습니다 x == y
.
In [18]: %timeit 'x' in ('x', )
10000000 loops, best of 3: 65.2 ns per loop
In [19]: %timeit 'x' == 'x'
10000000 loops, best of 3: 68 ns per loop
In [20]: %timeit 'x' in ('y', )
10000000 loops, best of 3: 73.4 ns per loop
In [21]: %timeit 'x' == 'y'
10000000 loops, best of 3: 56.2 ns per loop
x in (y, )
애프터 때문에 케이스 느린 is
비교가 실패의 in
오퍼레이터 (즉, 사용 정상 어떤지 검사에 빠진다 ==
) 비교가만큼의 시간이 소요되므로 ==
인해 터플을 생성하는 오버 헤드 느 전반적인 동작 렌더링 , 회원 등을 걷는 등
또한 다음 a in (b, )
경우 에만 더 빠릅니다 a is b
.
In [48]: a = 1
In [49]: b = 2
In [50]: %timeit a is a or a == a
10000000 loops, best of 3: 95.1 ns per loop
In [51]: %timeit a in (a, )
10000000 loops, best of 3: 140 ns per loop
In [52]: %timeit a is b or a == b
10000000 loops, best of 3: 177 ns per loop
In [53]: %timeit a in (b, )
10000000 loops, best of 3: 169 ns per loop
(이유 a in (b, )
보다 더 빨리 a is b or a == b
? 내 생각 엔이 적은 가상 머신의 지침이 될 것입니다 – a in (b, )
만 ~ 3 개 지침 a is b or a == b
보다 꽤 많은 VM 지침이 될 것입니다)
Veedrac의 대답 – https://stackoverflow.com/a/28889838/71522은 – 각시 발생 구체적으로 무엇에 더 많은 세부로 전환 ==
하고 in
및 읽기 가치가있다.