First stab at the Warnings PEP. I'm still working on the

implementation...
This commit is contained in:
Guido van Rossum 2000-12-07 17:38:50 +00:00
parent 9056227bb3
commit 5d702e543f
1 changed files with 328 additions and 0 deletions

View File

@ -19,6 +19,334 @@ Abstract
counter-proposal posted on the same date.
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:
sys.warn(message[, category])
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.
- To issue a warning from C:
int Py_Warn(char *message, PyObject *category)
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?]
Warnings Categories
The "warnings" module defines classes representing 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
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.
- UserWarning -- the default category for sys.warn()
- DeprecationWarning -- base category for warnings about deprecated
features
- SyntaxWarning -- base category for warnings about dubious
syntactic features
Other categories may be proposed during the review period for this
PEP.
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 Py_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 PyEval_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 regular expression that the warning message must
match
- module is a 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
The Warning class is derived from the built-in Exception class, so
that to turn a warning into an error we raise category(message).
The Warnings Output Hook
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 default implementation of this function writes the warning text
to sys.stderr, and shows the source line of the filename.
TO DO
- There should be a function sys.insertwarningfilter(message,
category, filename, lineno) that inserts items into the warnings
filter after some sanity checking.
- 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
Implementation
Here is a mostly functional implementation:
"""Prototype implementation of sys.warn() and related stuff."""
import sys, re, linecache, getopt
class Warning(Exception):
"""Base class for warning categories.
All warning categories must be subclasses of this.
"""
pass
class UserWarning(Warning):
"""Base class for warnings generated by user code."""
pass
class DeprecationWarning(Warning):
"""Base class for warnings about deprecated features."""
pass
class SyntaxWarning(Warning):
"""Base class for warnings about dubious syntax."""
pass
defaultaction = "default"
filter = []
onceregistry = {}
def warn(message, category=None, level=1):
"""Issue a warning, or maybe ignore it or raise an exception."""
# Check category argument
if category is None:
category = UserWarning
assert issubclass(category, Warning)
# Get context information
caller = sys._getframe(level)
globals = caller.f_globals
lineno = caller.f_lineno
module = globals['__name__']
filename = globals.get('__file__')
if not filename:
if module == "__main__":
filename = sys.argv[0]
if not filename:
filename = module
# Quick test for common case
registry = globals.setdefault("__warningregistry__", {})
key = (message, category, lineno)
if registry.get(key):
return
# Search the filter
for msg, cat, mod, ln, action in filter:
if (re.match(msg, message) and
issubclass(category, cat) and
re.match(mod, module) and
(ln == 0 or lineno == ln)):
break
else:
action = defaultaction
# Early exit actions
if action == "ignore":
registry[key] = 1
return
if action == "error":
raise category(message)
# Other actions
if action == "once":
registry[key] = 1
oncekey = (message, category)
if onceregistry.get(oncekey):
return
onceregistry[oncekey] = 1
elif action == "always":
pass
elif action == "module":
registry[key] = 1
altkey = (message, category, 0)
if registry.get(altkey):
return
registry[altkey] = 1
elif action == "default":
registry[key] = 1
else:
# Unrecognized actions are errors
raise RuntimeError(
"Unrecognized action (%s) in warnings.filter:\n %s" %
(`action`, str((cat, msg, mod, ln, action))))
# Print message and context
showwarning(message, category, filename, lineno)
def showwarning(message, category, filename, lineno):
print >>sys.stderr, "%s:%s: %s: %s" % (filename, lineno,
category.__name__, message)
line = linecache.getline(filename, lineno).strip()
if line:
print >>sys.stderr, " " + line
def setupfilter(args):
"""Set up the warnings filter based upon command line options.
Return remaining command line arguments.
Raise getopt.error if an unrecognized option is found.
"""
opts, args = getopt.getop(args, "")
return args
# Self-test
def _test():
hello = "hello world"
warn(hello)
warn(hello, UserWarning)
warn(hello, DeprecationWarning)
for i in range(3):
warn(hello)
filter.append(("", Warning, "", 0, "error"))
try:
warn(hello)
except Exception, msg:
print "Caught", msg.__class__.__name__ + ":", msg
else:
print "No exception"
filter[:] = []
filter.append(("", Warning, "", 0, "booh"))
try:
warn(hello)
except Exception, msg:
print "Caught", msg.__class__.__name__ + ":", msg
else:
print "No exception"
if __name__ == "__main__":
args = setupfilter(sys.argv[1:])
_test()
Local Variables:
mode: indented-text