Who should see Python deprecation warnings?
In early November, one sub-thread of a big discussion on preparing for the
Python 3.7 release focused on the await and async
identifiers. They will become keywords in 3.7, meaning that any code using
those names for any other purpose will break. Nick Coghlan observed that Python 3.6 does not warn
about the use of those names, calling it "a fairly major
oversight/bug
". In truth, though, Python 3.6 does emit
warnings in that case — but users rarely see them.
The reason for that comes down to the configuration of a relatively obscure module called warnings. The Python interpreter can generate quite a few warnings in various categories, many of which are likely to be seen as noise by users. The warnings module is used to emit warnings, but it also gives developers a way to bring back some silence by establishing a filter controlling which warnings will actually be printed out to the error stream. The default filter is:
ignore::DeprecationWarning ignore::PendingDeprecationWarning ignore::ImportWarning ignore::BytesWarning ignore::ResourceWarning
The first line filters out DeprecationWarning events, such as the warnings regarding await and async in the 3.6 release. Those warnings were also present in 3.5 as longer-term PendingDeprecationWarnings, which are also invisible by default.
As it happens, things were not always this way. While
PendingDeprecationWarning has always been filtered,
DeprecationWarning was visible by default until the
Python 2.7 and 3.2 releases. In a 2009 thread discussing the
change, Python benevolent dictator for life Guido van Rossum argued
that the deprecation warnings, while being useful to some developers, were
more often just "irrelevant noise
", especially for anybody who
does not actually work on the code in question:
The idea was fairly intensely debated, but silencing those warnings by default won out in the end.
In 2017, it has become evident that this decision has kept some important warnings out of the sight of people who should see them, with the result that many people may face an unpleasant surprise when an upgrade to 3.7 abruptly breaks previously working programs. That was the cue for a new intensely debated thread over whether deprecation warnings should be enabled again.
Neil Schemenauer started things off with a suggestion that the warnings should be re-enabled by default; Coghlan subsequently proposed reverting to the way things were. He went on to say that, if application developers don't want their users to see deprecation warnings, they should disable those warnings explicitly. The invisibility of deprecation warnings has hurt users, he said, and some classes of users in particular:
Application developers are, one hopes, using testing frameworks for their modules, and those frameworks typically turn the warnings back on. But the above-mentioned users will not be performing such testing and will be unnecessarily surprised if Python 3.7 breaks their scripts.
The proposal led to some familiar complaints, though. Van Rossum worried that it would inflict a bunch of warning noise on users of scripts who are in no position to fix them. Antoine Pitrou suggested that small-script developers would be deluged by warnings originating in modules that they import — warnings that, once again, they cannot fix. Over time, the thread seemed to coalesce on the idea that the warnings should not be re-enabled unconditionally; they should, instead, remain disabled for "third-party" code that the current user is unlikely to have control over.
That is a fine idea, with only one little problem: how does one define "third-party code" in this setting? There were a few ideas raised, such as emitting warnings for all code located under the directory containing the initial script, but the search for heuristics threatened to devolve into a set of complex special cases that nobody would be able to remember. So the solution that was written up by Coghlan as PEP 565 was rather simpler: enable DeprecationWarning in the __main__ module, while leaving it suppressed elsewhere. In essence, any code run directly by a user would have warnings enabled, while anything imported from another module would not.
This change will almost certainly not bring deprecation warnings to the attention of everybody who needs to see them. But it will cause them to be emitted for users who are running single-file scripts or typing commands directly at the Python interpreter. That solves what Coghlan sees as the biggest problem: casual Python scripters who will otherwise be unpleasantly surprised when a distribution upgrade causes their scripts to fail. It is a partial solution that appears to be better than the status quo.
Van Rossum agreed with that assessment. He
acknowledged that it's "not going to make everyone happy
", but
said that it's an improvement and that he intends to approve it in the near
future in the absence of more objections. Naturally, such a pronouncement
brought out some objections, but none of them would appear to have the
strength to keep PEP 565 from being a part of the Python 3.7
release. The 3.7 interpreter's usurpation of await and async
may be an unpleasant surprise to some users, but hopefully future
changes will be less surprising.
Index entries for this article | |
---|---|
Python | Deprecation |
Posted Dec 7, 2017 10:31 UTC (Thu)
by epa (subscriber, #39769)
[Link] (9 responses)
When an application is implemented in Python, it should have a separate 'build' step which at a minimum checks that each source file can be parsed and does any compile-time checking available. It's at that point that deprecation warnings like this one should be shown. Then whoever is packaging the software for distribution will be able to act on them accordingly (by setting a dependency on a particular Python version, or reporting the problem upstream, or just patching the code to use different names). I agree, it doesn't make sense for the end user to see them spat out at run time.
The same would apply to Python libraries: when the library is built into a redistributable package (.rpm or .deb) the warnings are shown to the packager. For developers who install and build libraries directly on the system using 'pip' or a similar tool, the developer is the person to be shown the compile-time warnings, and they should be prompted when they install it -- not every single time the code is run subsequently.
So there needs to be a stricter mode used by default for build and installation steps, and a looser one for the end user who runs the code.
Posted Dec 7, 2017 16:05 UTC (Thu)
by mb (subscriber, #50428)
[Link] (6 responses)
Posted Dec 7, 2017 16:13 UTC (Thu)
by NAR (subscriber, #1313)
[Link]
Posted Dec 7, 2017 16:24 UTC (Thu)
by epa (subscriber, #39769)
[Link] (2 responses)
For rpm and deb package building, I would expect the distribution's build farm to collect the build output and pass warnings along to the package maintainer, who can then decide whether to contact upstream. There too we suffer from far too much noise. Builds need a --quiet flag where progress messages aren't printed, only errors and warnings that require some human attention.
But either of these setups, although a bit awkward, is much preferable to waiting until the application is run and then splurging out strange messages at run time that the user can neither understand nor fix.
Posted Dec 7, 2017 18:21 UTC (Thu)
by mb (subscriber, #50428)
[Link] (1 responses)
Posted Dec 7, 2017 20:54 UTC (Thu)
by epa (subscriber, #39769)
[Link]
If the program must run on older Python versions, then you just don't use the new 'async' feature (and don't use that word as a variable name in your program either). If you want to use it, but conditionalized on newer versions, then I guess you have to have code compiled and loaded at run time. Naturally that won't be checked by any static, compile-time checker.
Posted Dec 14, 2017 22:50 UTC (Thu)
by ras (subscriber, #33059)
[Link] (1 responses)
I've written a fair few Python programs over the years and deployed them on production systems. These things get written, deployed then mostly forgotten about. In the early days I got caught several times by programs breaking after an upgrade because I had ignored depreciation warnings. Upgrading to the next distro version is stressful enough - adding to it like this is just insanity so I started to religiously pay attention to them.
Then this "improvement" came long. I understand the reasoning. It might even be true that most of the Python programs I run come from third parties where it applies (but that seems unlikely). Fortunately there is an easy fix for the stuff I write:
#!/usr/bin/python -W default
Posted Dec 18, 2017 20:17 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
Posted Dec 14, 2017 14:50 UTC (Thu)
by HelloWorld (guest, #56129)
[Link]
Given that deprecation warnings almost certainly happen much more often than language changes, I'm not sure how beneficial a separate build step would be.
Posted Dec 18, 2017 1:54 UTC (Mon)
by njs (subscriber, #40338)
[Link]
In particular, when distributing Python packages, the compilation is always done on the end-users system, either at install-time or at first import, *not* by the distributor. This is important because the person installing the package might be using a different version of Python than the end-user.
This is an intrinsic limit: if I test and upload a package using 3.5, then 3.6 adds a warning, there's no possible way Python can reach back in time and tell me about that. You could make it so that packages are tied to specific Python versions and 3.6 users can't get packages that were only tested on 3.5, but that would cause wayyyy worse problems then what this is trying to solve. 99.9% of packages work fine across Python versions.
Posted Dec 7, 2017 13:40 UTC (Thu)
by lkundrak (subscriber, #43452)
[Link] (5 responses)
They could just make the "async" and "async" special only in files marked with some pragma (whatewer would be the Python equivalent of "use 3.7;").
Posted Dec 7, 2017 15:34 UTC (Thu)
by anselm (subscriber, #2796)
[Link] (1 responses)
The main philosophical difference between the Perl crowd and the Python crowd is that on the whole, the Python crowd likes things to be straightforward and simple while the Perl crowd doesn't mind complexity if it lets people be lazy. Remember that Perl programmers consider laziness, impatience, and hubris virtues.
Generally the Python people would like keywords to be keywords and identifiers to be identifiers. Having a few identifiers become keywords, or vice versa, at the flick of a magic switch, is bascially not Pythonic; we can live with that in special circumstances where it makes the whole language simpler and more consistent (think of print becoming a proper function – i.e., less magical – in Python 3.x), but we don't want to carry that sort of baggage along indefinitely simply to save some people from having to change a bunch of asyncs into myasyncs (or whatever) once.
Posted Dec 7, 2017 16:32 UTC (Thu)
by epa (subscriber, #39769)
[Link]
Just as you specify 'python2' or 'python3' in the shebang line, there is a case for declaring exactly what version of Python the code is intended to run on. (Remember, explicit is better than implicit.) Then later versions, which introduce new keywords or other features, can either suppress those features to keep compatibility or cleanly fail with a message that the code needs porting. It doesn't by any means imply maintaining the compatibility code indefinitely.
Posted Dec 7, 2017 15:59 UTC (Thu)
by dskoll (subscriber, #1630)
[Link] (2 responses)
Perl uses sigils, so only bare words can ever clash with keywords, which limits the scope of the problem to subroutine names and file handles.
Posted Dec 7, 2017 16:09 UTC (Thu)
by NAR (subscriber, #1313)
[Link] (1 responses)
Posted Dec 7, 2017 20:30 UTC (Thu)
by dskoll (subscriber, #1630)
[Link]
Perl's predefined variables are all either punctuation like $@ or upper-case like %ENV. Once again, the potential for clashes is somewhat limited by convention.
Posted Dec 14, 2017 9:03 UTC (Thu)
by callegar (guest, #16148)
[Link] (1 responses)
Posted Dec 14, 2017 16:13 UTC (Thu)
by cortana (subscriber, #24596)
[Link]
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
But I don't think a static checker would work anyway for one simple practical reason:
What if your program must run on older Python versions? A version check (be it sys.version_info or hasattr or something similar) would be needed in the code. Old interpreters using the old API and new versions using the new API.
How do you handle that with the static checker?
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Compile-time versus run-time warnings
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?
Isn't this the classic situation for using environment variables? The developer declares himself as such by running with PYTHON_DEVEL=true (either by setting it directly or because his ide sets it for him) and sees all the warnings. Users don't. Plus, setuptools could be enhanced so that setup.py can lint the code.
Who should see Python deprecation warnings?
Who should see Python deprecation warnings?