For lists, both methods (in and index()) iterate over the list to check for the item you're looking for, unfortunately. They will stop iteration as soon as the result of the membership test is known, which means they will iterate to the end if the item is not found.
As far as I know, if you must work with lists, the not in construct is the most Python and the one you should go with (but you should dump those unnecessary parentheses).
If you don't need to specifically use a list, the built-in set type can often work in its place. The set is a data structure similar to the list, but it uses a hashing algorithm to test for the presence of an item, so if you're doing a lot of that kind of work, you may consider switching. Read the docs I've linked to though, because sets are unordered, so they don't support things like slicing or indexing.
Yes, you can plan for times when the item you're checking for is not present in your data structure. You're looking for a Try/Except Block:
example_list = [1,2,3]
try:
index_of_4 = example_list.index(4)
except ValueError:
print("Oops! 4 wasn't in the list!")
When you know exceptions may occur in your program, you can wrap the offending code in a block like this to gracefully catch and recover from exceptions. It is indeed good programming practice to recover from errors and exceptions as gracefully as you can, even if that means just printing an error message and exiting.
$ for i in 1 2 3 4 5 ; do python -mtimeit -s "l=range(int(1e$i))" "(-1) not in l" ; done