Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| PEP: 230 | |
| Title: Warning Framework | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: guido@python.org (Guido van Rossum) | |
| Status: Final | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Created: | |
| Python-Version: 2.1 | |
| Post-History: 05-Nov-2000 | |
| Abstract | |
| ======== | |
| This PEP proposes a C and Python level API, as well as command | |
| line flags, to issue warning messages and control what happens to | |
| them. This is mostly based on GvR's proposal posted to python-dev | |
| on 05-Nov-2000, with some ideas (such as using classes to | |
| categorize warnings) merged in from Paul Prescod's | |
| counter-proposal posted on the same date. Also, an attempt to | |
| implement the proposal caused several small tweaks. | |
| Motivation | |
| ========== | |
| With Python 3000 looming, it is necessary to start issuing | |
| warnings about the use of obsolete or deprecated features, in | |
| addition to errors. There are also lots of other reasons to be | |
| able to issue warnings, both from C and from Python code, both at | |
| compile time and at run time. | |
| Warnings aren't fatal, and thus it's possible that a program | |
| triggers the same warning many times during a single execution. | |
| It would be annoying if a program emitted an endless stream of | |
| identical warnings. Therefore, a mechanism is needed that | |
| suppresses multiple identical warnings. | |
| It is also desirable to have user control over which warnings are | |
| printed. While in general it is useful to see all warnings all | |
| the time, there may be times where it is impractical to fix the | |
| code right away in a production program. In this case, there | |
| should be a way to suppress warnings. | |
| It is also useful to be able to suppress specific warnings during | |
| program development, e.g. when a warning is generated by a piece | |
| of 3rd party code that cannot be fixed right away, or when there | |
| is no way to fix the code (possibly a warning message is generated | |
| for a perfectly fine piece of code). It would be unwise to offer | |
| to suppress all warnings in such cases: the developer would miss | |
| warnings about the rest of the code. | |
| On the other hand, there are also situations conceivable where | |
| some or all warnings are better treated as errors. For example, | |
| it may be a local coding standard that a particular deprecated | |
| feature should not be used. In order to enforce this, it is | |
| useful to be able to turn the warning about this particular | |
| feature into an error, raising an exception (without necessarily | |
| turning all warnings into errors). | |
| Therefore, I propose to introduce a flexible "warning filter" | |
| which can filter out warnings or change them into exceptions, | |
| based on: | |
| - Where in the code they are generated (per package, module, or | |
| function) | |
| - The warning category (warning categories are discussed below) | |
| - A specific warning message | |
| The warning filter must be controllable both from the command line | |
| and from Python code. | |
| APIs For Issuing Warnings | |
| ========================= | |
| - To issue a warning from Python:: | |
| import warnings | |
| warnings.warn(message[, category[, stacklevel]]) | |
| The category argument, if given, must be a warning category | |
| class (see below); it defaults to warnings.UserWarning. This | |
| may raise an exception if the particular warning issued is | |
| changed into an error by the warnings filter. The stacklevel | |
| can be used by wrapper functions written in Python, like this:: | |
| def deprecation(message): | |
| warn(message, DeprecationWarning, level=2) | |
| This makes the warning refer to the deprecation()'s caller, | |
| rather than to the source of deprecation() itself (since the | |
| latter would defeat the purpose of the warning message). | |
| - To issue a warning from C:: | |
| int PyErr_Warn(PyObject *category, char *message); | |
| Return 0 normally, 1 if an exception is raised (either because | |
| the warning was transformed into an exception, or because of a | |
| malfunction in the implementation, such as running out of | |
| memory). The category argument must be a warning category class | |
| (see below) or ``NULL``, in which case it defaults to | |
| ``PyExc_RuntimeWarning``. When ``PyErr_Warn()`` function returns 1, the | |
| caller should do normal exception handling. | |
| The current C implementation of ``PyErr_Warn()`` imports the | |
| warnings module (implemented in Python) and calls its ``warn()`` | |
| function. This minimizes the amount of C code that needs to be | |
| added to implement the warning feature. | |
| [XXX Open Issue: what about issuing warnings during lexing or | |
| parsing, which don't have the exception machinery available?] | |
| Warnings Categories | |
| =================== | |
| There are a number of built-in exceptions that represent warning | |
| categories. This categorization is useful to be able to filter | |
| out groups of warnings. The following warnings category classes | |
| are currently defined: | |
| - ``Warning`` -- this is the base class of all warning category | |
| classes and it itself a subclass of Exception | |
| - ``UserWarning`` -- the default category for ``warnings.warn()`` | |
| - ``DeprecationWarning`` -- base category for warnings about deprecated | |
| features | |
| - ``SyntaxWarning`` -- base category for warnings about dubious | |
| syntactic features | |
| - ``RuntimeWarning`` -- base category for warnings about dubious | |
| runtime features | |
| [XXX: Other warning categories may be proposed during the review | |
| period for this PEP.] | |
| These standard warning categories are available from C as | |
| ``PyExc_Warning``, ``PyExc_UserWarning``, etc. From Python, they are | |
| available in the ``__builtin__`` module, so no import is necessary. | |
| User code can define additional warning categories by subclassing | |
| one of the standard warning categories. A warning category must | |
| always be a subclass of the Warning class. | |
| The Warnings Filter | |
| =================== | |
| The warnings filter control whether warnings are ignored, | |
| displayed, or turned into errors (raising an exception). | |
| There are three sides to the warnings filter: | |
| - The data structures used to efficiently determine the | |
| disposition of a particular ``warnings.warn()`` or ``PyErr_Warn()`` | |
| call. | |
| - The API to control the filter from Python source code. | |
| - The command line switches to control the filter. | |
| The warnings filter works in several stages. It is optimized for | |
| the (expected to be common) case where the same warning is issued | |
| from the same place in the code over and over. | |
| First, the warning filter collects the module and line number | |
| where the warning is issued; this information is readily available | |
| through ``sys._getframe()``. | |
| Conceptually, the warnings filter maintains an ordered list of | |
| filter specifications; any specific warning is matched against | |
| each filter specification in the list in turn until a match is | |
| found; the match determines the disposition of the match. Each | |
| entry is a tuple as follows:: | |
| (category, message, module, lineno, action) | |
| - category is a class (a subclass of ``warnings.Warning``) of which | |
| the warning category must be a subclass in order to match | |
| - message is a compiled regular expression that the warning | |
| message must match (the match is case-insensitive) | |
| - module is a compiled regular expression that the module name | |
| must match | |
| - lineno is an integer that the line number where the warning | |
| occurred must match, or 0 to match all line numbers | |
| - action is one of the following strings: | |
| - "error" -- turn matching warnings into exceptions | |
| - "ignore" -- never print matching warnings | |
| - "always" -- always print matching warnings | |
| - "default" -- print the first occurrence of matching warnings | |
| for each location where the warning is issued | |
| - "module" -- print the first occurrence of matching warnings | |
| for each module where the warning is issued | |
| - "once" -- print only the first occurrence of matching | |
| warnings | |
| Since the ``Warning`` class is derived from the built-in ``Exception`` | |
| class, to turn a warning into an error we simply raise | |
| ``category(message)``. | |
| Warnings Output And Formatting Hooks | |
| ==================================== | |
| When the warnings filter decides to issue a warning (but not when | |
| it decides to raise an exception), it passes the information about | |
| the function ``warnings.showwarning(message, category, filename, lineno)``. | |
| The default implementation of this function writes the warning text | |
| to ``sys.stderr``, and shows the source line of the filename. It has | |
| an optional 5th argument which can be used to specify a different | |
| file than ``sys.stderr``. | |
| The formatting of warnings is done by a separate function, | |
| ``warnings.formatwarning(message, category, filename, lineno)``. This | |
| returns a string (that may contain newlines and ends in a newline) | |
| that can be printed to get the identical effect of the | |
| ``showwarning()`` function. | |
| API For Manipulating Warning Filters | |
| ==================================== | |
| :: | |
| warnings.filterwarnings(message, category, module, lineno, action) | |
| This checks the types of the arguments, compiles the message and | |
| module regular expressions, and inserts them as a tuple in front | |
| of the warnings filter. | |
| :: | |
| warnings.resetwarnings() | |
| Reset the warnings filter to empty. | |
| Command Line Syntax | |
| =================== | |
| There should be command line options to specify the most common | |
| filtering actions, which I expect to include at least: | |
| - suppress all warnings | |
| - suppress a particular warning message everywhere | |
| - suppress all warnings in a particular module | |
| - turn all warnings into exceptions | |
| I propose the following command line option syntax:: | |
| -Waction[:message[:category[:module[:lineno]]]] | |
| Where: | |
| - 'action' is an abbreviation of one of the allowed actions | |
| ("error", "default", "ignore", "always", "once", or "module") | |
| - 'message' is a message string; matches warnings whose message | |
| text is an initial substring of 'message' (matching is | |
| case-insensitive) | |
| - 'category' is an abbreviation of a standard warning category | |
| class name **or** a fully-qualified name for a user-defined | |
| warning category class of the form [package.]module.classname | |
| - 'module' is a module name (possibly package.module) | |
| - 'lineno' is an integral line number | |
| All parts except 'action' may be omitted, where an empty value | |
| after stripping whitespace is the same as an omitted value. | |
| The C code that parses the Python command line saves the body of | |
| all -W options in a list of strings, which is made available to | |
| the warnings module as sys.warnoptions. The warnings module | |
| parses these when it is first imported. Errors detected during | |
| the parsing of sys.warnoptions are not fatal; a message is written | |
| to sys.stderr and processing continues with the option. | |
| Examples: | |
| ``-Werror`` | |
| Turn all warnings into errors | |
| ``-Wall`` | |
| Show all warnings | |
| ``-Wignore`` | |
| Ignore all warnings | |
| ``-Wi:hello`` | |
| Ignore warnings whose message text starts with "hello" | |
| ``-We::Deprecation`` | |
| Turn deprecation warnings into errors | |
| ``-Wi:::spam:10`` | |
| Ignore all warnings on line 10 of module spam | |
| ``-Wi:::spam -Wd:::spam:10`` | |
| Ignore all warnings in module spam except on line 10 | |
| ``-We::Deprecation -Wd::Deprecation:spam`` | |
| Turn deprecation warnings into errors except in module spam | |
| Open Issues | |
| =========== | |
| Some open issues off the top of my head: | |
| - What about issuing warnings during lexing or parsing, which | |
| don't have the exception machinery available? | |
| - The proposed command line syntax is a bit ugly (although the | |
| simple cases aren't so bad: ``-Werror``, ``-Wignore``, etc.). Anybody | |
| got a better idea? | |
| - I'm a bit worried that the filter specifications are too | |
| complex. Perhaps filtering only on category and module (not on | |
| message text and line number) would be enough? | |
| - There's a bit of confusion between module names and file names. | |
| The reporting uses file names, but the filter specification uses | |
| module names. Maybe it should allow filenames as well? | |
| - I'm not at all convinced that packages are handled right. | |
| - Do we need more standard warning categories? Fewer? | |
| - In order to minimize the start-up overhead, the warnings module | |
| is imported by the first call to ``PyErr_Warn()``. It does the | |
| command line parsing for ``-W`` options upon import. Therefore, it | |
| is possible that warning-free programs will not complain about | |
| invalid ``-W`` options. | |
| Rejected Concerns | |
| ================= | |
| Paul Prescod, Barry Warsaw and Fred Drake have brought up several | |
| additional concerns that I feel aren't critical. I address them | |
| here (the concerns are paraphrased, not exactly their words): | |
| - Paul: ``warn()`` should be a built-in or a statement to make it easily | |
| available. | |
| Response: "from warnings import warn" is easy enough. | |
| - Paul: What if I have a speed-critical module that triggers | |
| warnings in an inner loop. It should be possible to disable the | |
| overhead for detecting the warning (not just suppress the | |
| warning). | |
| Response: rewrite the inner loop to avoid triggering the | |
| warning. | |
| - Paul: What if I want to see the full context of a warning? | |
| Response: use ``-Werror`` to turn it into an exception. | |
| - Paul: I prefer ":\*:\*:" to ":::" for leaving parts of the warning | |
| spec out. | |
| Response: I don't. | |
| - Barry: It would be nice if lineno can be a range specification. | |
| Response: Too much complexity already. | |
| - Barry: I'd like to add my own warning action. Maybe if 'action' | |
| could be a callable as well as a string. Then in my IDE, I | |
| could set that to "mygui.popupWarningsDialog". | |
| Response: For that purpose you would override | |
| ``warnings.showwarning()``. | |
| - Fred: why do the Warning category classes have to be in | |
| ``__builtin__``? | |
| Response: that's the simplest implementation, given that the | |
| warning categories must be available in C before the first | |
| ``PyErr_Warn()`` call, which imports the warnings module. I see no | |
| problem with making them available as built-ins. | |
| Implementation | |
| ============== | |
| Here's a prototype implementation: | |
| http://sourceforge.net/patch/?func=detailpatch&patch_id=102715&group_id=5470 | |
| .. | |
| Local Variables: | |
| mode: indented-text | |
| indent-tabs-mode: nil | |
| End: |