https://github.com/kata198/func_timeout/pull/18

diff --git a/func_timeout/dafunc.py b/func_timeout/dafunc.py
index 681a01c..afa193f 100644
--- a/func_timeout/dafunc.py
+++ b/func_timeout/dafunc.py
@@ -1,4 +1,3 @@
-
 # vim: set ts=4 sw=4 expandtab :
 
 '''
@@ -9,27 +8,25 @@
 '''
 
 import copy
-import inspect
-import threading
-import time
 import types
 import sys
 
 from .exceptions import FunctionTimedOut
 from .StoppableThread import StoppableThread
 
-try:
-    from .py3_raise import raise_exception
-except SyntaxError:
-    from .py2_raise import raise_exception
-except ImportError:
-    from .py2_raise import raise_exception
-
 from functools import wraps
 
 __all__ = ('func_timeout', 'func_set_timeout')
 
 
+# PEP 409 - Raise with the chained exception context disabled
+#  This, in effect, prevents the "funcwrap" wrapper ( chained
+#   in context to an exception raised here, due to scope )
+# Only available in python3.3+
+def raise_exception(exception):
+    raise exception[0] from None
+
+
 def func_timeout(timeout, func, args=(), kwargs=None):
     '''
         func_timeout - Runs the given function for up to #timeout# seconds.
@@ -175,7 +172,7 @@ def calculateTimeout(*args, **kwargs):
             try:
                 timeout = float(timeout)
             except:
-                raise ValueError('timeout argument must be a float/int for number of seconds, or a function/lambda which gets passed the function arguments and returns a calculated timeout (as float or int). Passed type: < %s > is not of any of these, and cannot be converted to a float.' %( timeout.__class__.__name__, ))
+                raise ValueError(f'timeout argument must be a float/int for number of seconds, or a function/lambda which gets passed the function arguments and returns a calculated timeout (as float or int). Passed type: < {timeout.__class__.__name__} > is not of any of these, and cannot be converted to a float.')
 
 
     if not allowOverride and not isTimeoutAFunction:
diff --git a/func_timeout/exceptions.py b/func_timeout/exceptions.py
index cd0d130..016320f 100644
--- a/func_timeout/exceptions.py
+++ b/func_timeout/exceptions.py
@@ -69,11 +69,11 @@ def getMsg(self):
         else:
             timedOutFuncName = 'Unknown Function'
         if self.timedOutAfter is not None:
-            timedOutAfterStr = "%f" %(self.timedOutAfter, )
+            timedOutAfterStr = f"{self.timedOutAfter:f}"
         else:
             timedOutAfterStr = "Unknown"
 
-        return 'Function %s (args=%s) (kwargs=%s) timed out after %s seconds.\n' %(timedOutFuncName, repr(self.timedOutArgs), repr(self.timedOutKwargs), timedOutAfterStr)
+        return f'Function {timedOutFuncName} (args={repr(self.timedOutArgs)}) (kwargs={repr(self.timedOutKwargs)}) timed out after {timedOutAfterStr} seconds.\n'
 
     def retry(self, timeout=RETRY_SAME_TIMEOUT):
         '''
diff --git a/func_timeout/py2_raise.py b/func_timeout/py2_raise.py
deleted file mode 100644
index 7bace72..0000000
--- a/func_timeout/py2_raise.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-# Python2 allows specifying an alternate traceback.
-def raise_exception(exception):
-    raise exception[0] , None , exception[0].__traceback__
diff --git a/func_timeout/py3_raise.py b/func_timeout/py3_raise.py
deleted file mode 100644
index dde334b..0000000
--- a/func_timeout/py3_raise.py
+++ /dev/null
@@ -1,7 +0,0 @@
-
-# PEP 409 - Raise with the chained exception context disabled
-#  This, in effect, prevents the "funcwrap" wrapper ( chained
-#   in context to an exception raised here, due to scope )
-# Only available in python3.3+
-def raise_exception(exception):
-    raise exception[0] from None
diff --git a/setup.py b/setup.py
index f5767b8..62031f3 100755
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@
         with open('README.rst', 'rt') as f:
             long_description = f.read()
     except Exception as e:
-        sys.stderr.write('Error reading from README.rst: %s\n' %(str(e),))
+        sys.stderr.write(f'Error reading from README.rst: {str(e)}\n')
         log_description = summary
 
     setup(name='func_timeout',
@@ -44,12 +44,10 @@
             classifiers=['Development Status :: 5 - Production/Stable',
                          'Programming Language :: Python',
                          'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)',
-                         'Programming Language :: Python :: 2',
-                          'Programming Language :: Python :: 2.7',
-                          'Programming Language :: Python :: 3.4',
-                          'Programming Language :: Python :: 3.5',
                           'Programming Language :: Python :: 3.6',
                           'Programming Language :: Python :: 3.7',
+                          'Programming Language :: Python :: 3.8',
+                          'Programming Language :: Python :: 3.9',
                           'Topic :: Software Development :: Libraries :: Python Modules'
             ]
     )
diff --git a/testit.py b/testit.py
index 0ff5ea2..709d622 100755
--- a/testit.py
+++ b/testit.py
@@ -17,19 +17,19 @@ def doit(howmany):
 if __name__ == '__main__':
 
     print ( "Should get return value of 23:" )
-    print ( "\tGot Return: %s\n" %(str(func_timeout(4, doit, args=(6,))),) )
+    print ( f"\tGot Return: {str(func_timeout(4, doit, args=(6,)))}\n" )
 
     print ( "\nShould time out (exception):" )
     myException = None
     try:
-        print ("\tGot Return: %s\n" %(str(func_timeout(1, doit, kwargs={'howmany' : 16})),))
+        print (f"\tGot Return: {str(func_timeout(1, doit, kwargs={'howmany': 16}))}\n")
     except FunctionTimedOut as e:
-        sys.stderr.write('\tGot Exception: %s\n' %(str(e),))
+        sys.stderr.write(f'\tGot Exception: {str(e)}\n')
         myException = e
         pass
 
     print ( "\nRetrying with longer timeout, should get 16+17=33:" )
     if myException is not None:
-        print ( "\nGot: %s\n" %( str(myException.retry(2.5)), ) )
+        print ( f"\nGot: {str(myException.retry(2.5))}\n" )
     else:
         sys.stderr.write('Did not get exception before?\n')
diff --git a/tests/FuncTimeoutTests/TestUtils.py b/tests/FuncTimeoutTests/TestUtils.py
index fdb9181..30f6b51 100644
--- a/tests/FuncTimeoutTests/TestUtils.py
+++ b/tests/FuncTimeoutTests/TestUtils.py
@@ -1,4 +1,3 @@
-
 # vim: set ts=4 sw=4 expandtab :
 
 '''
@@ -93,7 +92,7 @@ def getSleepLambda(sleepTime):
 
     
 
-    return eval('''lambda a, b : int(bool(time.sleep(%f))) + a + b''' %(_sleepTime,))
+    return eval(f'''lambda a, b : int(bool(time.sleep({_sleepTime:f}))) + a + b''')
 
 
 def getSleepLambdaWithArgs(sleepTime, args):
@@ -145,7 +144,7 @@ def getSleepLambdaWithArgs(sleepTime, args):
 
 
 #    print ( 'Function is: %s' %('''lambda %s : int(bool(time.sleep(%f))) + %s''' %(argStr, sleepTime, sumStr, )  ) )
-    return eval('''lambda %s : int(bool(time.sleep(%f))) + %s''' % (argStr, sleepTime, sumStr, ) )
+    return eval(f'''lambda {argStr} : int(bool(time.sleep({sleepTime:f}))) + {sumStr}''' )
 
 
 def compareTimes(timeEnd, timeStart, cmpTime, roundTo=None, deltaFixed=.05, deltaPct=None):
diff --git a/tests/FuncTimeoutTests/test_Basic.py b/tests/FuncTimeoutTests/test_Basic.py
index 2ed1e5c..fc518ea 100755
--- a/tests/FuncTimeoutTests/test_Basic.py
+++ b/tests/FuncTimeoutTests/test_Basic.py
@@ -38,9 +38,9 @@ def test_funcTimeout(self):
         try:
             result = func_timeout(2.5, sleepFunction, args=(5, 13))
         except FunctionTimedOut as te:
-            raise AssertionError('Got unexpected timeout at 2.5 second timeout for 2.00 second function: %s' %(str(te),))
+            raise AssertionError(f'Got unexpected timeout at 2.5 second timeout for 2.00 second function: {str(te)}')
 
-        assert result == expectedResult , 'Got wrong return from func_timeout.\nGot:       %s\nExpected:  %s\n' %(repr(result), repr(expectedResult))
+        assert result == expectedResult , f'Got wrong return from func_timeout.\nGot:       {repr(result)}\nExpected:  {repr(expectedResult)}\n'
 
         gotException = False
         try:
@@ -53,9 +53,9 @@ def test_funcTimeout(self):
         try:
             result = func_timeout(2.5, sleepFunction, args=(5,), kwargs={ 'b' : 13})
         except FunctionTimedOut as te:
-            raise AssertionError('Got unexpected timeout at 2.5 second timeout for 2.00 second function: %s' %(str(te), ))
+            raise AssertionError(f'Got unexpected timeout at 2.5 second timeout for 2.00 second function: {str(te)}')
         except Exception as e:
-            raise AssertionError('Got unknown exception mixing args and kwargs: < %s >  %s' %(e.__class__.__name__, str(e)))
+            raise AssertionError(f'Got unknown exception mixing args and kwargs: < {e.__class__.__name__} >  {str(e)}')
 
         assert result == expectedResult , 'Got wrong result when mixing args and kwargs'
 
@@ -76,7 +76,7 @@ def test_retry(self):
         endTime = time.time()
 
         assert gotException , 'Expected to get exception'
-        assert compareTimes(endTime, startTime, .8, 3, deltaFixed=.15) == 0 , 'Expected to wait .8 seconds. Was: %f - %f = %f' %(endTime, startTime, round(endTime - startTime, 3))
+        assert compareTimes(endTime, startTime, .8, 3, deltaFixed=.15) == 0 , f'Expected to wait .8 seconds. Was: {endTime:f} - {startTime:f} = {round(endTime - startTime, 3):f}'
 
         gotException = False
         startTime = time.time()
@@ -143,8 +143,8 @@ def test_exception(self):
 
         assert gotException , 'Expected to get exception'
 
-        assert 'timed out after ' in functionTimedOut.msg  , 'Expected message to be constructed. Got: %s' %(repr(functionTimedOut.msg), )
-        assert round(functionTimedOut.timedOutAfter, 1) == .5 , 'Expected timedOutAfter to equal timeout ( .5 ). Got: %s' %(str(round(functionTimedOut.timedOutAfter, 1)), )
+        assert 'timed out after ' in functionTimedOut.msg  , f'Expected message to be constructed. Got: {repr(functionTimedOut.msg)}'
+        assert round(functionTimedOut.timedOutAfter, 1) == .5 , f'Expected timedOutAfter to equal timeout ( .5 ). Got: {str(round(functionTimedOut.timedOutAfter, 1))}'
         assert functionTimedOut.timedOutFunction == sleepFunction , 'Expected timedOutFunction to equal sleepFunction'
         assert functionTimedOut.timedOutArgs == (5, 19) , 'Expected args to equal (5, 19)'
         assert functionTimedOut.timedOutKwargs == {} , 'Expected timedOutKwargs to equal {}'
@@ -160,7 +160,7 @@ def test_instantiateExceptionNoArgs(self):
             msg2 = exc.getMsg()
 
         except Exception as _e:
-            sys.stderr.write('Got unexpected exception in test_instantiateExceptionNoArgs with no arguments. %s  %s\n\n' %(str(type(_e)), str(_e)))
+            sys.stderr.write(f'Got unexpected exception in test_instantiateExceptionNoArgs with no arguments. {str(type(_e))}  {str(_e)}\n\n')
             gotException = True
 
         assert gotException is False, 'Expected to be able to create FunctionTimedOut exception without arguments.'
@@ -173,7 +173,7 @@ def test_instantiateExceptionNoArgs(self):
             msg2 = str(exc.getMsg())
 
         except Exception as _e:
-            sys.stderr.write('Got unexpected exception in test_instantiateExceptionNoArgs with fixed message string. %s  %s\n\n' %(str(type(_e)), str(_e)))
+            sys.stderr.write(f'Got unexpected exception in test_instantiateExceptionNoArgs with fixed message string. {str(type(_e))}  {str(_e)}\n\n')
             gotException = True
 
         assert gotException is False , 'Expected to be able to create a FunctionTimedOut exception with a fixed message.'
diff --git a/tests/FuncTimeoutTests/test_Decorator.py b/tests/FuncTimeoutTests/test_Decorator.py
index ca149ca..b5a8430 100755
--- a/tests/FuncTimeoutTests/test_Decorator.py
+++ b/tests/FuncTimeoutTests/test_Decorator.py
@@ -331,7 +331,7 @@ def doSleepFuncUnder(a, b):
         except FunctionTimedOut as fte2:
             gotException = True
         except Exception as e:
-            raise AssertionError('Got exception trying to retry with same timeout:  < %s > : %s' %(e.__name__, str(e)))
+            raise AssertionError(f'Got exception trying to retry with same timeout:  < {e.__name__} > : {str(e)}')
         endTime = time.time()
 
         assert gotException , 'Expected to get exception with calculated same 80% timeout on retry'
@@ -345,7 +345,7 @@ def doSleepFuncUnder(a, b):
         except FunctionTimedOut as fte2:
             gotException = True
         except Exception as e:
-            raise AssertionError('Got exception trying to retry with same timeout:  < %s > : %s' %(e.__name__, str(e)))
+            raise AssertionError(f'Got exception trying to retry with same timeout:  < {e.__name__} > : {str(e)}')
         endTime = time.time()
 
         assert not gotException , 'Expected to get exception with calculated 80% timeout on retry ( None ) [ No timeout ]'
@@ -361,7 +361,7 @@ def doSleepFuncUnder(a, b):
         except FunctionTimedOut as fte2:
             gotException = True
         except Exception as e:
-            raise AssertionError('Got exception trying to retry with same timeout:  < %s > : %s' %(e.__name__, str(e)))
+            raise AssertionError(f'Got exception trying to retry with same timeout:  < {e.__name__} > : {str(e)}')
         endTime = time.time()
 
         assert gotException , 'Expected to get exception with calculated 80% timeout overriden by 60% timeout on retry'
@@ -375,7 +375,7 @@ def doSleepFuncUnder(a, b):
         except FunctionTimedOut as fte2:
             gotException = True
         except Exception as e:
-            raise AssertionError('Got exception trying to retry with same timeout:  < %s > : %s' %(e.__name__, str(e)))
+            raise AssertionError(f'Got exception trying to retry with same timeout:  < {e.__name__} > : {str(e)}')
         endTime = time.time()
 
         assert not gotException , 'Expected to get exception with calculated 80% timeout overriden by 150% timeout on retry'
diff --git a/tests/FuncTimeoutTests/test_StoppableThread.py b/tests/FuncTimeoutTests/test_StoppableThread.py
index 1120a53..eeabf7f 100755
--- a/tests/FuncTimeoutTests/test_StoppableThread.py
+++ b/tests/FuncTimeoutTests/test_StoppableThread.py
@@ -38,9 +38,9 @@ def test_funcTimeout(self):
         try:
             result = func_timeout(1.5, sleepFunction, args=(5, 13))
         except FunctionTimedOut as te:
-            raise AssertionError('Got unexpected timeout at 1.5 second timeout for 1.25 second function: %s' %(str(te),))
+            raise AssertionError(f'Got unexpected timeout at 1.5 second timeout for 1.25 second function: {str(te)}')
 
-        assert result == expectedResult , 'Got wrong return from func_timeout.\nGot:       %s\nExpected:  %s\n' %(repr(result), repr(expectedResult))
+        assert result == expectedResult , f'Got wrong return from func_timeout.\nGot:       {repr(result)}\nExpected:  {repr(expectedResult)}\n'
 
         gotException = False
         try:
@@ -53,9 +53,9 @@ def test_funcTimeout(self):
         try:
             result = func_timeout(1.5, sleepFunction, args=(5,), kwargs={ 'b' : 13})
         except FunctionTimedOut as te:
-            raise AssertionError('Got unexpected timeout at 1.5 second timeout for 1.25 second function: %s' %(str(te), ))
+            raise AssertionError(f'Got unexpected timeout at 1.5 second timeout for 1.25 second function: {str(te)}')
         except Exception as e:
-            raise AssertionError('Got unknown exception mixing args and kwargs: < %s >  %s' %(e.__class__.__name__, str(e)))
+            raise AssertionError(f'Got unknown exception mixing args and kwargs: < {e.__class__.__name__} >  {str(e)}')
 
         assert result == expectedResult , 'Got wrong result when mixing args and kwargs'
 
@@ -76,7 +76,7 @@ def test_retry(self):
         endTime = time.time()
 
         assert gotException , 'Expected to get exception'
-        assert compareTimes(endTime, startTime, .3, 3, .15, None) == 0 , 'Expected to wait .3 seconds. Was: %f - %f = %f' %(endTime, startTime, round(endTime - startTime, 3))
+        assert compareTimes(endTime, startTime, .3, 3, .15, None) == 0 , f'Expected to wait .3 seconds. Was: {endTime:f} - {startTime:f} = {round(endTime - startTime, 3):f}'
 
         gotException = False
         startTime = time.time()
@@ -143,8 +143,8 @@ def test_exception(self):
 
         assert gotException , 'Expected to get exception'
 
-        assert 'timed out after ' in functionTimedOut.msg  , 'Expected message to be constructed. Got: %s' %(repr(functionTimedOut.msg), )
-        assert round(functionTimedOut.timedOutAfter, 1) == .3 , 'Expected timedOutAfter to equal timeout ( .3 ). Got: %s' %(str(round(functionTimedOut.timedOutAfter, 1)), )
+        assert 'timed out after ' in functionTimedOut.msg  , f'Expected message to be constructed. Got: {repr(functionTimedOut.msg)}'
+        assert round(functionTimedOut.timedOutAfter, 1) == .3 , f'Expected timedOutAfter to equal timeout ( .3 ). Got: {str(round(functionTimedOut.timedOutAfter, 1))}'
         assert functionTimedOut.timedOutFunction == sleepFunction , 'Expected timedOutFunction to equal sleepFunction'
         assert functionTimedOut.timedOutArgs == (5, 19) , 'Expected args to equal (5, 19)'
         assert functionTimedOut.timedOutKwargs == {} , 'Expected timedOutKwargs to equal {}'
diff --git a/tests/runTests.py b/tests/runTests.py
index 0c6ec12..0a63444 100755
--- a/tests/runTests.py
+++ b/tests/runTests.py
@@ -37,7 +37,7 @@ def find_mod(modName):
                 # imp.find_module raises import error if cannot find,
                 #   but find_spec just returns None
                 # So simulate the ImportError for common interface
-                raise ImportError('No module named %s' %(modName, ))
+                raise ImportError(f'No module named {modName}')
 
             return modSpec
 
@@ -183,9 +183,9 @@ def try_pip_install():
         sys.stderr.write('Failed to install GoodTests via pip module. Falling back to pip executable...\n\n')
 
     pipPath = os.path.dirname(sys.executable) + os.sep + 'pip'
-    print ( 'Searching for pip at "%s"' %(pipPath, ) )
+    print ( f'Searching for pip at "{pipPath}"' )
     if not os.path.exists(pipPath):
-        print ( '"%s" does not exist. Scanning PATH to locate a usable pip executable' %(pipPath, ))
+        print ( f'"{pipPath}" does not exist. Scanning PATH to locate a usable pip executable')
         pipPath = None
         searchResults = findExecutable('pip')
         if not searchResults['success']:
@@ -194,8 +194,8 @@ def try_pip_install():
 
         pipPath = searchResults['path']
 
-    print ( 'Found pip executable at "%s"' %(pipPath, ) )
-    print ( "Executing:  %s %s 'install' 'GoodTests'" %(sys.executable, pipPath) )
+    print ( f'Found pip executable at "{pipPath}"' )
+    print ( f"Executing:  {sys.executable} {pipPath} 'install' 'GoodTests'" )
     pipe = subprocess.Popen([sys.executable, pipPath, 'install', 'GoodTests'], shell=False, env=os.environ)
     res = pipe.wait()
     
@@ -240,7 +240,7 @@ def download_goodTests(GOODTESTS_URL=None):
         if str != bytes:
             contents = contents.decode('ascii')
     except Exception as e:
-        sys.stderr.write('Failed to download GoodTests.py from "%s"\n%s\n' %(GOODTESTS_URL, str(e)))
+        sys.stderr.write(f'Failed to download GoodTests.py from "{GOODTESTS_URL}"\n{str(e)}\n')
         sys.stderr.write('\nTrying pip.\n')
         res = try_pip_install()
         if res != 0:
@@ -250,7 +250,7 @@ def download_goodTests(GOODTESTS_URL=None):
         with open('GoodTests.py', 'w') as f:
             f.write(contents)
     except Exception as e:
-        sys.stderr.write('Failed to write to GoodTests.py\n%s\n' %(str(e,)))
+        sys.stderr.write(f'Failed to write to GoodTests.py\n{str(e)}\n')
         return 1
     try:
         os.chmod('GoodTests.py', 0o775)
@@ -311,7 +311,7 @@ def main(thisDir=None, additionalArgs=[], MY_PACKAGE_MODULE=None, ALLOW_SITE_INS
             return downloadRet
         goodTestsInfo = findGoodTests()
         if goodTestsInfo['success'] is False:
-            sys.stderr.write('Could not download or find GoodTests.py. Try to download it yourself using "pip install GoodTests", or wget %s\n' %( GOODTESTS_URL,))
+            sys.stderr.write(f'Could not download or find GoodTests.py. Try to download it yourself using "pip install GoodTests", or wget {GOODTESTS_URL}\n')
             return 1
 
     baseName = os.path.basename(MY_PACKAGE_MODULE)
@@ -339,13 +339,13 @@ def main(thisDir=None, additionalArgs=[], MY_PACKAGE_MODULE=None, ALLOW_SITE_INS
             except ImportError as e:
                 sys.path = oldSysPath
                 if not ALLOW_SITE_INSTALL:
-                    sys.stderr.write('Cannot find "%s" locally.\n' %(MY_PACKAGE_MODULE,))
+                    sys.stderr.write(f'Cannot find "{MY_PACKAGE_MODULE}" locally.\n')
                     return 2
                 else:
                     try:
                         __import__(baseName)
                     except:
-                        sys.stderr.write('Cannot find "%s" locally or in global python path.\n' %(MY_PACKAGE_MODULE,))
+                        sys.stderr.write(f'Cannot find "{MY_PACKAGE_MODULE}" locally or in global python path.\n')
                         return 2
 
             if foundIt is True:
@@ -372,16 +372,16 @@ def main(thisDir=None, additionalArgs=[], MY_PACKAGE_MODULE=None, ALLOW_SITE_INS
             eName = e.message.split()[-1]
 
         if eName != MY_PACKAGE_MODULE:
-            sys.stderr.write('Error while importing %s: %s\n Likely this is another dependency that needs to be installed\nPerhaps run "pip install %s" or install the providing package.\n\n' %(eName, str(e), eName))
+            sys.stderr.write(f'Error while importing {eName}: {str(e)}\n Likely this is another dependency that needs to be installed\nPerhaps run "pip install {eName}" or install the providing package.\n\n')
             return 1
-        sys.stderr.write('Could not import %s. Either install it or otherwise add to PYTHONPATH\n%s\n' %(MY_PACKAGE_MODULE, str(e)))
+        sys.stderr.write(f'Could not import {MY_PACKAGE_MODULE}. Either install it or otherwise add to PYTHONPATH\n{str(e)}\n')
         return 1
 
     if not os.path.isdir(MY_TEST_DIRECTORY):
         if not os.path.exists(MY_TEST_DIRECTORY):
-            sys.stderr.write('Cannot find test directory: %s\n' %(MY_TEST_DIRECTORY,))
+            sys.stderr.write(f'Cannot find test directory: {MY_TEST_DIRECTORY}\n')
         else:
-            sys.stderr.write('Provided test directory, "%s" is not a directory.\n' %(MY_TEST_DIRECTORY,))
+            sys.stderr.write(f'Provided test directory, "{MY_TEST_DIRECTORY}" is not a directory.\n')
         return 3
 
     sys.stdout.write('Starting test..\n')