You can use zip and islice to construct an iterator with its own "future" available to it.
next(
filter(
lambda pair: isprime(pair[1]), # The predicate applied to a pair
zip(xs, islice(xs, 1, None)) # The iterator and its 'future'
),
(None, None) # A default pair in case no matches are found
)[0] # Retrieve the 'current' entry from the matching pair
At each step you have a pair that you can think of as (present, future) and the predicate will be applied to future. At the end, we unpack the present, which represents the last non-matching entry in the iterator.
Note that this implementation, as presented, does not return the last entry in the list, because inherently if you zip [a, b, c] against its own shifted version you end up with one shorter than the other. You can use zip_longest (from itertools) to overcome this, but you'll need to handle the fillvalue (usually None) in your predicate.
As noted in a comment, this does not work for generators because it will consume the generator. Using a fold (reduce in Python), though, it becomes easier. First, in a more "Pythonic" presentation, the function looks something like this:
def fold(hist, cur):
stop, prev = hist
if stop:
return hist
if isprime(cur):
return (True, prev)
return (False, cur)
The first element of the tuple serves as a "stop" marker, and the second is our "needle." You can use this with reduce thus:
reduce(fold, xs, (False, None))[1]
That's not exactly a "one liner," but we can compress it into a lambda:
reduce(lambda z, x: (z if z[0] else ((1, z[1]) if isprime(x) else (0, x))), xs, (0, None))[1]