First stab at the Warnings PEP. I'm still working on the
implementation...
This commit is contained in:
parent
9056227bb3
commit
5d702e543f
328
pep-0230.txt
328
pep-0230.txt
|
@ -19,6 +19,334 @@ Abstract
|
||||||
counter-proposal posted on the same date.
|
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:
|
Local Variables:
|
||||||
mode: indented-text
|
mode: indented-text
|
||||||
|
|
Loading…
Reference in New Issue