Siempre dan el mismo resultado.
De hecho, not 'ham' in 'spam and eggs'parece ser un caso especial para realizar una única operación "no en", en lugar de una operación "en" y luego negar el 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
Al principio pensé que siempre daban el mismo resultado, pero que notpor sí solo era simplemente un operador de negación lógica de baja precedencia, que podía aplicarse a in btan fácilmente como cualquier otra expresión booleana, mientras que not inera un operador separado por conveniencia y claridad. .
¡El desmontaje de arriba fue revelador! Parece que, si bien notes obvio que es un operador de negación lógico, el formulario not a in bestá en mayúsculas y minúsculas, por lo que en realidad no utiliza el operador general. Esto hace not a in bliteralmente la misma expresión que a not in b, en lugar de simplemente una expresión que da como resultado el mismo valor.
not x in xsen los documentos.