|
|
Subscribe / Log in / New account

Who should see Python deprecation warnings?

By Jonathan Corbet
December 6, 2017
As all Python developers discover sooner or later, Python is a rapidly evolving language whose community occasionally makes changes that can break existing programs. The switch to Python 3 is the most prominent example, but minor releases can include significant changes as well. The CPython interpreter can emit warnings for upcoming incompatible changes, giving developers time to prepare their code, but those warnings are suppressed and invisible by default. Work is afoot to make them visible, but doing so is not as straightforward as it might seem.

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:

If you download and install some 3rd party code, deprecation warnings about that code are *only noise* since they are not yours to fix. Warnings in a dynamic language work very different than warnings in a compiled language.

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:

We've been running the current experiment for 7 years, and the main observable outcome has been folks getting surprised by breaking changes in CPython releases, especially folks that primarily use Python interactively (e.g. for data analysis), or as a scripting engine (e.g. for systems administration).

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
PythonDeprecation


to post comments

Compile-time versus run-time warnings

Posted Dec 7, 2017 10:31 UTC (Thu) by epa (subscriber, #39769) [Link] (9 responses)

Even in a dynamic language like Python, you can distinguish between compile-time and run-time warnings. Use of the keywords 'await' and 'async' can mostly be detected at compile time. (You have to accept that evaluating strings of code at run time will fall outside the check.)

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.

Compile-time versus run-time warnings

Posted Dec 7, 2017 16:05 UTC (Thu) by mb (subscriber, #50428) [Link] (6 responses)

Does anybody actually read all these build messages? I would not notice such a warning in the noise.

Compile-time versus run-time warnings

Posted Dec 7, 2017 16:13 UTC (Thu) by NAR (subscriber, #1313) [Link]

The continuous integration system should have a job that runs some lint-like program on the code and raise warnings/errors based on the Python warnings...

Compile-time versus run-time warnings

Posted Dec 7, 2017 16:24 UTC (Thu) by epa (subscriber, #39769) [Link] (2 responses)

Indeed, so much noise scrolls past you'd miss it. I think there is a case for tools like 'pip' to handle the warnings separately, requiring manual confirmation to proceed. (Just as if the test suite fails you'd have to manually override that in order to install.)

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.

Compile-time versus run-time warnings

Posted Dec 7, 2017 18:21 UTC (Thu) by mb (subscriber, #50428) [Link] (1 responses)

So far so good.
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

Posted Dec 7, 2017 20:54 UTC (Thu) by epa (subscriber, #39769) [Link]

For things like a new keyword 'async', there is not much the Python program can do at run time to avoid the problem. The code will parse differently on different versions, and what's valid syntax on one will be a compile-time syntax error on the other. Short of putting your whole program inside an 'eval', or having source files loaded at run time conditionally, you can't switch between two different syntaxes. It is not like the situation where an API changes and you can just decide to call the new or the old form.

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.

Compile-time versus run-time warnings

Posted Dec 14, 2017 22:50 UTC (Thu) by ras (subscriber, #33059) [Link] (1 responses)

> Does anybody actually read all these build messages?

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

Compile-time versus run-time warnings

Posted Dec 18, 2017 20:17 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

Interesting, Python handles this as one would expect even though argv[1] is actually "-W default" (since shebang lines only support a single argument). Not what I'd have expected, but hey, learn something new all the time :) .

Compile-time versus run-time warnings

Posted Dec 14, 2017 14:50 UTC (Thu) by HelloWorld (guest, #56129) [Link]

This might work for language changes like the introduction of async/await, but it won't work for, say, deprecation warnings for library functions. The problem is that in Python you generally don't know what an identifier refers to until that bit of code is executed, hence you can't tell if it is deprecated. This is one of the many reasons why dynamic typing is a mistake.

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.

Compile-time versus run-time warnings

Posted Dec 18, 2017 1:54 UTC (Mon) by njs (subscriber, #40338) [Link]

Python does have a compile/run-time distinction, and this particular warning is actually issued at compile time. But because Python compiles things automatically when needed, there's no reason to think that the person compiling the code is the person who wrote it.

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.

Who should see Python deprecation warnings?

Posted Dec 7, 2017 13:40 UTC (Thu) by lkundrak (subscriber, #43452) [Link] (5 responses)

Seems like Python could borrow an idea or two from Perl where the situation with new keywords seems to be a solved problem.

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;").

Who should see Python deprecation warnings?

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.

Who should see Python deprecation warnings?

Posted Dec 7, 2017 16:32 UTC (Thu) by epa (subscriber, #39769) [Link]

That's fine, except that upgrading from Python 3.6 to 3.7 (or whatever) is also 'the flick of a magic switch' and causes some identifiers to become keywords, or vice versa. And from the program's point of view it's considerably more magic and more hidden than an explicit declaration at the top. (The upgrade to a later Python version might even happen automatically as part of system package updates.)

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.

Who should see Python deprecation warnings?

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.

Who should see Python deprecation warnings?

Posted Dec 7, 2017 16:09 UTC (Thu) by NAR (subscriber, #1313) [Link] (1 responses)

Perl also has predefined variables.

Who should see Python deprecation warnings?

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.

Who should see Python deprecation warnings?

Posted Dec 14, 2017 9:03 UTC (Thu) by callegar (guest, #16148) [Link] (1 responses)

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?

Posted Dec 14, 2017 16:13 UTC (Thu) by cortana (subscriber, #24596) [Link]

That way, I forsee myself being belaid by hundreds of thousands of deprecation warnings for code I import, rather than just for my own code. The same problem makes it difficult to build my own C++ code with -Wall -Werror; I have always assumed that the Boost developers are clever enough to avoid the problems that the compiler hints at when -Wstrict-aliasing is turned on, but I would still like those warnings to fail the build when found in my own code...


Copyright © 2017, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds