0

Consider the following piece of code:

import shutil
import time
t = time.time()
exception = None
while time.time() < (t + 10.0):
    try:
        shutil.rmtree('/path-to-non-existent-directory')
        break
    except OSError as exception:
        pass
    time.sleep(0.1)
else:
    if exception:
        raise exception

In Python 2.7 this code is perfectly valid but in Python 3.7 I get the following warning:

Local variable exception might be referenced before assignment

In the else clause.

Does anyone have an idea what is wrong with this snippet when run in Python 3.7?

Eric Salemi
  • 119
  • 9
  • 1
    @ThomasWeller Doesn't `except OSError as exception:` set `exception` to something that isn't false? – Barmar Apr 24 '19 at 15:16
  • [this question](https://stackoverflow.com/questions/43974798/local-variable-might-be-referenced-before-assignment-python/43974850) is related, but the answers there all say that initializing the variable before the loop should prevent the warning. – Barmar Apr 24 '19 at 15:23
  • @Eric Salemi - I have tried this in Python 3.7, and it returns an error saying 'Timer', 'shutil', 'cls' and 'time' are not defined. But in Python 2.7, 'Timer' (only) appears to be not defined. You should iniitalize your variables so minimalize errors. – Justin Apr 24 '19 at 15:57
  • @Eric Salemi - Add an 'import time' to initialize the 'time' variable. – Justin Apr 24 '19 at 15:58
  • How exactly are you getting that warning? Sounds more like an overzealous linter than anything else. I can't reproduce that warning simply by *running* the code. – chepner Apr 24 '19 at 16:51
  • I get that warning in PyCharm Ultimate. – Eric Salemi Apr 28 '19 at 21:33

2 Answers2

1

In Python 3, to resolve a circular reference issue caused by the introduction of the __traceback__ attribute, an except target is automatically deleted at the end of an except block. It behaves as if you had written

except OSError as exception:
    pass
    del exception

This is documented in PEP 3110.

If you want to preserve the exception object, you should save it to a second variable:

except OSError as exception:
    saved_exception = exception

exception will still be deleted, but you can use saved_exception to inspect the exception object after the except block is over.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • This answer perfectly explains why the original code shows no warning in Python 2.7. It feels slightly more verbose and hackish to use a second variable name but i guess I can live with that. – Eric Salemi Apr 28 '19 at 21:30
0

Python is a block scoped language, you can't reference a variable outside of the block in which it was defined and you can't use a new value outside of the block in which that variable was updated.

In other words, you can't reference the exception variable's error data outside of the except block, if you try to do so, the value of the exception variable will be None (which you set at the top-level).

Try moving the contents of your else block to the except block and get rid of the exception = None and if exception, like this:

timer = Timer(10.0)
while timer.alive:
  try:
    shutil.rmtree(cls.workspace)
    break
  except OSError as exception:
    raise exception
  time.sleep(0.1)

If you don't want fatal errors, you could just use the print() function instead of the raise keyword:

timer = Timer(10.0)
while timer.alive:
  try:
    shutil.rmtree(cls.workspace)
    break
  except OSError as exception:
    print(exception)
  time.sleep(0.1)

Here is another example (which won't work):

def hello():
  message = "Hello World"

print(message)

Because it'll raise the the following error:

NameError: name 'message' is not defined

NOTE: I'd advice against calling your exception exception, because there's an error class called Exception and doing so could lead to confusion later on.

Good luck.

Malekai
  • 4,765
  • 5
  • 25
  • 60
  • 1
    `exception` is assigned a value before the loop even begins. – chepner Apr 24 '19 at 16:48
  • @chepner Thanks for pointing that out, I hadn't noticed it. – Malekai Apr 24 '19 at 16:49
  • 1
    Python is not block scoped, and the problem with referring to `exception` has nothing to do with scope. [`except` targets are deleted at the end of an `except` block in Python 3](https://www.python.org/dev/peps/pep-3110/#semantic-changes) to resolve a circular reference introduced by the `__traceback__` attribute. – user2357112 Apr 28 '19 at 21:19
  • @LogicalBranch AFAIK only `def` and `class` keywords are creating new scope in Python. Any other keyword such as `for`, `while` or `with` do not create scope so that names created within them will be available outside of them and names created before them will be available within them. – Eric Salemi Apr 28 '19 at 21:23