-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Reset the interactive namespace __warningregistry__ before executing code #6680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Despite the fact that the fix is simple, I love to see this kind of explanation in the PR first message. 👍 for me. |
|
@damianavila: Eh, it saves time in the end ;-) |
|
After the discussion in #6611, this makes sense to me. I'd like to hear from @takluyver on the implementation. |
|
Let's not push it into |
I don't see a problem... probably safer than the current one. |
That should be fine. Just register it as a hook during initialization. |
|
There are three separate issues here: (a) to use a hook or just to hard-code it in the file, (b) whether to clear the registry before or after the code runs, (c) whether to clear the registry in Re: (a): I don't see much point in decoupling for the sake of decoupling -- there's a tradeoff between the benefits of flexibility and the costs of reduce locality making it harder to read and understand the code. In this case, the Re: (b): IMO it's better to clear the Re: (c): My reasoning for pushing the registry clearing down into So, that's why I wrote the patch the way I did :-). If on reflection you'd prefer something else let me know. |
|
Thanks for explaining your reasoning:
|
e09673f to
48f4d74
Compare
|
So I made an attempt to get this working as a hook (see the current version of this PR), and noticed two issues:
So..... how do you want to do this? |
|
Registering hooks doesn't have to be done inside interactiveshell code - anything that gets a reference to the shell object (like extensions) can use Maybe the Going one step further, maybe |
|
@takluyver it sounds like cleaning up event reset should be a separate PR. What should we do here so this can be merged, and the other stuff investigated separately? |
The whole idea of the EventManager is that you can register hooks without worrying about what hooks other pieces of code might be registering. The reset methods violate this separation of concerns, since they will blow away everyone else's hooks too. (See ipythongh-6680 for an example of this breaking things.) Since there is never any safe way to use them, we simply remove them entirely.
…code Fixes ipython#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.
48f4d74 to
61431d7
Compare
|
As a start, I went ahead and made a commit that just removes the |
|
I'm fine with this as-is. @takluyver? |
|
Yep. Thanks for your patience, @njsmith . |
Reset the interactive namespace __warningregistry__ before executing code
The whole idea of the EventManager is that you can register hooks without worrying about what hooks other pieces of code might be registering. The reset methods violate this separation of concerns, since they will blow away everyone else's hooks too. (See ipythongh-6680 for an example of this breaking things.) Since there is never any safe way to use them, we simply remove them entirely.
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.:
Oo, right, guess I gotta be careful of those zeros -- thanks, numpy,
for giving me that warning!
A few days later:
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()callsanotherpkg.bad_func()which issues a warning, then this warning willstill only be displayed once, even if multiple cells call
somepkg.foo(). But if cell 5 and cell 592 both callanotherpkg.bad_func()directly, then both will get warnings.(Important exception: if
foo()is defined interactively, and callsanotherpkg.bad_func(), then every cell that callsfoo()will displaythe 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()callsbar()which callsbaz()which callssome_deprecated_api(), thenbaz()will get counted as being "responsible", and the warning system will
make a note that the usage of
some_deprecated_api()insidebaz()hasalready 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 aglobal (module-level) variable there named
__warningregistry__, andthen, 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.