1. 7
    1. 4


      The tenacity library comes with an intriguing mix of iterators and context managers to allow the users to retry blocks with this syntax:

      from tenacity import Retrying, stop_after_attempt
      for attempt in Retrying(stop=stop_after_attempt(3)):
          with attempt:
              1 / 0

      You can use the same mechanism yourself, by creating and linking a custom context manager and iterator:

      class YouAndIhaveUnfinishedBusiness:
          def __init__(self, notify_success, attempt_number):
              self.notify_success = notify_success
              self.attempt = attempt_number
          def __enter__(self):
          def __exit__(self, exc_type, exc_value, traceback):
              if exc_value:
                  print(f"You have disappointed me {self.attempt} time(s).")
              return True
      class DoOrDoNotThereIsNoTry:
          def __init__(self, max_attempts):
              self.max_attempts = max_attempts
              self.success = False
          def __iter__(self):
              for i in range(self.max_attempts):
                  yield YouAndIhaveUnfinishedBusiness(self.succeed, i+1)
                  if self.success:
                      print("You are ready for the Kumite.")
                  print("We trained him wrong on purpose, as a joke.")
          def succeed(self):
              self.success = True
    2. 1

      The issue with this pattern is that you create a hard API requirement that isn’t enforceable through the Python API. Aka nothing would stop me from mistakenly doing, and as a reviewer that is unaware of the retrying API, I would never spot this bug:

      for attempt in Retrying(stop=stop_after_attempt(3)): 

      For a reader that didn’t went through the documentation, it isn’t obvious at all what the with attempt is meant for. This pattern try to be too much clever for little to no gain over the existing decorator pattern.

      Decorator are also easy to use as scope with inlined function. Nothing stop you from doing:

      def func():
        foo = load_foo()
        def get_foo_data():
          return requests.get(foo.url).text
        data = get_foo_data()
        return json.loads(data)