To be "pythonic" you'll want to write code that conforms to the Zen of Python and a relevant rules here are:
Explicit is better than implicit.
Readability counts.
You should be explicit about the number of failures you allow, or the number of failures remaining. In the former case:
for number_of_failures in range(MAX_FAILURE):
...
expresses that intent, but is clunky because each time through the loop you are not necessarily failing, so you'd have to be be contorted in counting successes, which would impact readability. You can go with:
number_of_failures = 0
while number_of_failures < MAX_FAILURES:
...
if this_is_a_failure_case:
number_of_failures += 1
...
This is perfectly fine, as it says "if I haven't failed the maximum number of times, keep going. This is a little better than:
number_of_failures = 0
while True:
...
if this_is_a_failure_case:
number_of_failures += 1
...
if number_of_failures == MAX_FAILURES: #you could use >= but this isn't necessary
break
which hides the exit case. There are times when it is perfectly fine to exit the loop in the middle, but in your case, aborting after N failures is so crucial to what you want to do, that the condition should be in the while-loop condition.
You can also rephrase in terms of number of failures remaining:
failures_remaining = MAX_FAILURES
while failures_remaining > 0:
...
if this_is_a_failure_case:
failures_remaining -= 1
...
If you like functional programming, you can get rid of the loop and use tail recursion:
def do_the_thing():
def try_it(failures_remaining = MAX_FAILURES):
...
failed = ....
...
try_it(failures_remaining-1 if failed else failures_remaining)
try_it()
but IMHO that isn't really an improvement. In Python, we like to be direct and meaningful. Counters may sound clunky, but if you phrase things well (after all you are counting failures, right?) you are safely in the realm of Pythonic code.