The two forms make identical bytecode, as you can clearly verify:
>>> import dis
>>> dis.dis(compile('if x not in d: pass', '', 'exec'))
1 0 LOAD_NAME 0 (x)
3 LOAD_NAME 1 (d)
6 COMPARE_OP 7 (not in)
9 JUMP_IF_FALSE 4 (to 16)
12 POP_TOP
13 JUMP_FORWARD 1 (to 17)
>> 16 POP_TOP
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
>>> dis.dis(compile('if not x in d: pass', '', 'exec'))
1 0 LOAD_NAME 0 (x)
3 LOAD_NAME 1 (d)
6 COMPARE_OP 7 (not in)
9 JUMP_IF_FALSE 4 (to 16)
12 POP_TOP
13 JUMP_FORWARD 1 (to 17)
>> 16 POP_TOP
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
so obviously they're semantically identical.
As a matter of style, PEP 8 does not mention the issue.
Personally, I strongly prefer the if x not in y
form -- that makes it immediately clear that not in
is a single operator, and "reads like English". if not x in y
may mislead some readers into thinking it means if (not x) in y
, reads a bit less like English, and has absolutely no compensating advantages.