diff --git a/pep-0230.txt b/pep-0230.txt index ea9445bd0..34d3a8bd3 100644 --- a/pep-0230.txt +++ b/pep-0230.txt @@ -16,7 +16,8 @@ Abstract 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. + counter-proposal posted on the same date. Also, an attempt to + implement the proposal caused several small tweaks. Motivation @@ -74,40 +75,54 @@ APIs For Issuing Warnings - To issue a warning from Python: - sys.warn(message[, category]) + 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. + 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 Py_Warn(char *message, PyObject *category) + int PyErr_Warn(PyObject *category, char *message); - Return 0 normally, 1 if an exception is raised. The category - argument must be a warning category class (see below). When - Py_Warn() function returns 1, the caller should do normal - exception handling. [Question: what about issuing warnings - during lexing or parsing, which don't have the exception - machinery available?] + 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 - The "warnings" module defines classes representing warning + 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 classes defined in this module have - no semantics attached to them and are never instantiated -- only - their names are used for filtering (see the section on the - warnings filter below). The following warnings category classes + out groups of warnings. The following warnings category classes are currently defined: - Warning -- this is the base class of all warning category - classes. A warning category must always be a subclass of this - class. + classes and it itself a subclass of Exception - - UserWarning -- the default category for sys.warn() + - UserWarning -- the default category for warnings.warn() - DeprecationWarning -- base category for warnings about deprecated features @@ -115,8 +130,19 @@ Warnings Categories - SyntaxWarning -- base category for warnings about dubious syntactic features - Other categories may be proposed during the review period for this - PEP. + - 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 @@ -127,7 +153,8 @@ The Warnings Filter There are three sides to the warnings filter: - The data structures used to efficiently determine the - disposition of a particular Py_Warn() call. + disposition of a particular warnings.warn() or PyErr_Warn() + call. - The API to control the filter from Python source code. @@ -139,7 +166,7 @@ The Warnings Filter First, the warning filter collects the module and line number where the warning is issued; this information is readily available - through PyEval_GetFrame(). + through sys._getframe(). Conceptually, the warnings filter maintains an ordered list of filter specifications; any specific warning is matched against @@ -152,10 +179,11 @@ The Warnings Filter - 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 regular expression that the warning message must - match + - message is a compiled regular expression that the warning + message must match - - module is a regular expression that the module name must match + - 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 @@ -177,37 +205,40 @@ The Warnings Filter - "once" -- print only the first occurrence of matching warnings - The Warning class is derived from the built-in Exception class, so - that to turn a warning into an error we raise category(message). + Since the Warning class is derived from the built-in Exception + class, to turn a warning into an error we simply raise + category(message). -The Warnings Output Hook +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 sys.showwarning(message, category, filename, lineno). + 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. + 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 - sys.filterwarnings(message, category, module, lineno, action) + warnings.filterwarnings(message, category, module, lineno, action) - This checks the types of the arguments and inserts them as a tuple - in front of the warnings filter. + 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. - sys.resetwarnings() + warnings.resetwarnings() Reset the warnings filter to empty. - sys.setupwarnings(args) - - Parse command line options and initialize the warnings filter - accordingly. The argument should be sys.argv[1:] or equivalent. - Unrecognized options raise getopt.error. The return value is a - list containing the remaining (non-option) arguments. - Command Line Syntax @@ -246,9 +277,12 @@ Command Line Syntax All parts except 'action' may be omitted, where an empty value after stripping whitespace is the same as an omitted value. - Each -W option results into a call to sys.filterwarnings(); thus - later -W options override earlier -W options for warnings they - both match. + 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: @@ -281,36 +315,12 @@ Open Issues Some open issues off the top of my head: - - The proposal has all the Python API functions in the sys module, - except that the warning categories are in the warnings module. - Perhaps everything should be in the warnings module (like the - prototype implementation)? Or perhaps warn() should be promoted - to a built-in (i.e. in the __builtin__ module)? + - What about issuing warnings during lexing or parsing, which + don't have the exception machinery available? - - It's tempting to leave the implementation in Python and add an - absolute minimal amount of C code, only to make the standard - warning categories available from C code. The Py_Warn() - function could call warnings.warn(). Similarly, the Python - main() function could collect -W options and pass them to - warnings.setupwarnings(). - - - The prototype implements a third argument to warn(): - - warn(message, category=UserWarning, level=1) - - The 'level' argument could 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 (the latter - would defeat the purpose of the warning message). - - - The proposed command line syntax is ugly (although the simple - cases aren't so bad: -Werror, -Wignore, etc.). Anybody got a - better idea? + - 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 @@ -322,7 +332,42 @@ Open Issues - I'm not at all convinced that packages are handled right. - - Better names for the various API functions? + - 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 has brought up several additional concerns that I + feel aren't critical. I address them here (the concerns are + paraphrased, not necessarily exactly Paul's 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. Implementation