Eles sempre dão o mesmo resultado.
Na verdade, not 'ham' in 'spam and eggs'parece ter um caso especial para executar uma única operação "não ativada", em vez de uma operação "interna" e, em seguida, negar o resultado:
>>> import dis
>>> def notin():
'ham' not in 'spam and eggs'
>>> dis.dis(notin)
2 0 LOAD_CONST 1 ('ham')
3 LOAD_CONST 2 ('spam and eggs')
6 COMPARE_OP 7 (not in)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
>>> def not_in():
not 'ham' in 'spam and eggs'
>>> dis.dis(not_in)
2 0 LOAD_CONST 1 ('ham')
3 LOAD_CONST 2 ('spam and eggs')
6 COMPARE_OP 7 (not in)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
>>> def not__in():
not ('ham' in 'spam and eggs')
>>> dis.dis(not__in)
2 0 LOAD_CONST 1 ('ham')
3 LOAD_CONST 2 ('spam and eggs')
6 COMPARE_OP 7 (not in)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
>>> def noteq():
not 'ham' == 'spam and eggs'
>>> dis.dis(noteq)
2 0 LOAD_CONST 1 ('ham')
3 LOAD_CONST 2 ('spam and eggs')
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 POP_TOP
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
A princípio pensei que eles sempre davam o mesmo resultado, mas que notpor si só era simplesmente um operador de negação lógico de baixa precedência, que poderia ser aplicado a in btão facilmente quanto qualquer outra expressão booleana, ao passo que not inera um operador separado por conveniência e clareza .
A desmontagem acima foi reveladora! Parece que, embora notobviamente seja um operador de negação lógico, a forma not a in bé casada especial de forma que não está realmente usando o operador geral. Isso torna not a in bliteralmente a mesma expressão que a not in b, em vez de meramente uma expressão que resulta no mesmo valor.
not x in xsnos documentos.