ipython/ipython — pull request #6680 by njsmith (2014-10-11 03:13:27+00:00)
Title: Reset the interactive namespace __warningregistry__ before executing code

Fixes #6611.

Idea:

Right now, people often don't see important warnings when running
code in IPython, because (to a first approximation) any given warning
will only issue once per session. Blink and you'll miss it! This is a
very common contributor to confused emails to numpy-discussion. E.g.:

In [5]: 1 / my_array_with_random_contents
/home/njs/.user-python2.7-64bit-3/bin/ipython:1: RuntimeWarning:
divide by zero encountered in divide
#!/home/njs/.user-python2.7-64bit-3/bin/python
Out[5]:
array([ 1.77073316, -2.29765021, -2.01800811, ..., 1.13871243,
-1.08302964, -8.6185091 ])

Oo, right, guess I gotta be careful of those zeros -- thanks, numpy,
for giving me that warning!

A few days later:

In [592]: 1 / some_other_array
Out[592]:
array([ 3.07735763, 0.50769289, 0.83984078, ..., -0.67563917,
-0.85736257, -1.36511271])

Oops, it turns out that this array had a zero in it too, and that's
going to bite me later. But no warning this time!

The effect of this commit is to make it so that warnings triggered by
the code in cell 5 do *not* suppress warnings triggered by the code in
cell 592. Note that this only applies to warnings triggered *directly*
by code entered interactively -- if somepkg.foo() calls
anotherpkg.bad_func() which issues a warning, then this warning will
still only be displayed once, even if multiple cells call
somepkg.foo(). But if cell 5 and cell 592 both call
anotherpkg.bad_func() directly, then both will get warnings.

(Important exception: if foo() is defined *interactively*, and calls
anotherpkg.bad_func(), then every cell that calls foo() will display
the warning again. This is unavoidable without fixes to CPython
upstream.)

Explanation:

Python's warning system has some weird quirks. By default, it tries to
suppress duplicate warnings, where "duplicate" means the same warning
message triggered twice by the same line of code. This requires
determining which line of code is responsible for triggering a
warning, and this is controlled by the stacklevel= argument to
warnings.warn. Basically, though, the idea is that if foo() calls
bar() which calls baz() which calls some_deprecated_api(), then baz()
will get counted as being "responsible", and the warning system will
make a note that the usage of some_deprecated_api() inside baz() has
already been warned about and doesn't need to be warned about
again. So far so good.

To accomplish this, obviously, there has to be a record of somewhere
which line this was. You might think that this would be done by
recording the filename:linenumber pair in a dict inside the warnings
module, or something like that. You would be wrong.

What actually happens is that the warnings module will use stack
introspection to reach into baz()'s execution environment, create a
global (module-level) variable there named __warningregistry__, and
then, inside this dictionary, record just the line number. Basically,
it assumes that any given module contains only one line 1, only one
line 2, etc., so storing the filename is irrelevant. Obviously for
interactive code this is totally wrong -- all cells share the same
execution environment and global namespace, and they all contain a new
line 1. Currently the warnings module treats these as if they were all
the same line.

In fact they are not the same line; once we have executed a given
chunk of code, we will never see those particular lines again. As soon
as a given chunk of code finishes executing, its line number labels
become meaningless, and the corresponding warning registry entries
become meaningless as well. Therefore, with this patch we delete the
__warningregistry__ each time we execute a new block of code.