issues with doctest and threads

Tim Peters tim.peters at gmail.com
Tue Aug 9 10:49:49 EDT 2005


[Michele Simionato]
> Thank you for your replies Jeff & Tim. The snippet I submitted is
> unfortunate, since I was writing an example (for a Python course I am
> going to give in September) to show that you cannot reliably assume
> that you will get exactly 9 dots, because of the limitations of 'sleep'.
> Mindlessly, I have cut & pasted that snippet, but my real question
> was not "how many dots I get", it was:  "why the error message talks
> about 'thread' not being in the globals?"  It's true that I can avoid it with
> a thread.join() (which I had forgot), but still I really cannot understand  the
> reason for such message.

Because the program is buggy:  synchronizing threads isn't a "do it if
you feel like it" thing, it's essential to correct threaded behavior. 
If you're going to show students bad thread practice, they're going to
get mysteries a lot deeper and more damaging than this one <0.5 wink>.

Add some more prints:

"""
>>> import time, threading
>>> def example():
...     thread.out = []
...     while thread.running:
...         time.sleep(.01)
...         print [11]
...         thread.out.append(".")
...         print [12]
...     print [13]
>>> thread = threading.Thread(None, example)
>>> thread.running = True; thread.start()
>>> time.sleep(.1)
>>> thread.running = False
>>> print thread.out
['.', '.', '.', '.', '.', '.', '.', '.', '.']
"""

if __name__ == "__main__":
   import doctest
   doctest.testmod()
   print [2]

Here's a typical run on my box:

File "blah.py", line 13, in __main__
Failed example:
    time.sleep(.1)
Expected nothing
Got:
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
    [11]
    [12]
**********************************************************************
1 items had failures:
   1 of   7 in __main__
***Test Failed*** 1 failures.
[2]
[11]
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Code\python\lib\threading.py", line 444, in __bootstrap
    self.run()
  File "C:\Code\python\lib\threading.py", line 424, in run
    self.__target(*self.__args, **self.__kwargs)
  File "<doctest __main__[1]>", line 6, in example
NameError: global name 'thread' is not defined

Note that [2] is printed _while_ the spawned thread is still running
([13] is never printed):  the call to doctest.testmod() is completely
finished, but you're still letting a thread spawned _by_ doctest run. 
The environment doctest set up for that thread is gone too.  Although
it doesn't actually matter in this specific example, because the main
thread (not just doctest) is also entirely done, the Python
interpreter starts tearing itself down. Why that last doesn't matter
in this example would take some time to explain; I don't think it's
needed here, because the test case got into mortal trouble for an
earlier reason.

> Why it is so misleading?

Simply because bad thread programming has allowed a thread to run
beyond the time resources it relies on have vanished.  It may sound
harsh, but this is tough love <wink>:  it's pilot error.

> Can something be done about it?

Properly synchronize the thread, to enforce what the code requires but
cannot hope to obtain by blind luck.  All it takes is the
thread.join() I suggested.  I don't understand why you're fighting
that, because it's basic proper thread practice -- it's not like I
suggested an obscure expert-level hack here.  If a student doesn't
know to join() a thread before they rely on that thread being done,
their thread career will be an endless nightmare.

All that said, this specific failure would _happen_ to go away too, if
in doctest's DocTestRunner.run(), the final 'test.globs.clear()" were
removed.  If you feel it's necessary to let threads spawned by a
doctest run beyond the time doctest completes, you can arrange to
invoke DocTestRunner.run() with clear_globs=False.  That's not an
intended use case, but it will work.  The intended use case is
explained in run's docstring:

        The examples are run in the namespace `test.globs`.  If
        `clear_globs` is true (the default), then this namespace will
        be cleared after the test runs, to help with garbage
        collection.  If you would like to examine the namespace after
        the test completes, then use `clear_globs=False`.



More information about the Python-list mailing list