code: def retry(num_attempts=3, exception_class=Exception, log=None, sleeptime=1):
      
  
      def decorator(func):
          @functools.wraps(func)
          def wrapper(*args, **kwargs):
              for i in range(num_attempts):
                  try:
                      return func(*args, **kwargs)
                  except exception_class as e:
                      if i == num_attempts - 1:
                          raise
                      else:
                          if log:
                              log.warn('Failed with error %r, trying again', e)
                          sleep(sleeptime)
  
          return wrapper
  
      return decorator
docs: >>> def fail():
      ...     runs[0] += 1
      ...     raise ValueError()
      >>> runs = [0]; retry(sleeptime=0)(fail)()
      Traceback (most recent call last):
      ...
      ValueError
      >>> runs
      [3]
      >>> runs = [0]; retry(2, sleeptime=0)(fail)()
      Traceback (most recent call last):
      ...
      ValueError
      >>> runs
      [2]
      >>> runs = [0]; retry(exception_class=IndexError, sleeptime=0)(fail)()
      Traceback (most recent call last):
      ...
      ValueError
      >>> runs
      [1]
      >>> logger = DoctestLogger()
      >>> runs = [0]; retry(log=logger, sleeptime=0)(fail)()
      Traceback (most recent call last):
      ...
      ValueError
      >>> runs
      [3]
      >>> logger.print_logs()
      Failed with error ValueError(), trying again
      Failed with error ValueError(), trying again