[Python-Dev] Warnings PEP
Guido van Rossum
guido@python.org
Mon, 06 Nov 2000 16:02:27 -0500
Paul, thanks for submitting a warnings framework. I'd like to give
some feedback, comparing it to my own proposal. Please also give
explicit feedback on mine!
[Paul Prescod]
> Abstract
>
> This PEP describes a generalized warning mechanism for Python 2.1. The
> primary purpose of this mechanism is to alert the programmer or user
> of a program to potential or likely errors which, for whatever reason,
> are not considered exception-worthy. For example, this might be done
> to keep old code working during a transitional period or to alert the
> programmer or user of a recoverable error.
What's missing here is a set of requirements. Comparing your proposal
to my requirements, I find that you are not addressing two
requirements that are important in my mind: a convenient and efficient
C API, and a mechanism that prints a warning message only the first
time that the warning is issued. These are important to me, because I
expect that most warnings will probably be generated by C code, and in
order to be useful we must avoid mindless repetition. If a function
divides two integers using the / operator, this is being detected by C
code (the int or long division implementation) and we only want to
print the warning once per program execution and per source location.
My expectation is that if a single occurrence (in the program) of a
warning condition caused an endless sequence of warnings to be spit
out, people would quickly grow a habit of disabling all warnings, thus
defeating the purposes.
Warnings are more a human engineering issue than a technical issue!
That's also why I am emphasizing a C API -- I want to make it real
easy to ussue quality warnings in the runtime. It's also why I
specify a rich (probably too rich!) filtering mechanism.
> Syntax
>
> assert >> cls, test[[[, arg], arg]...]
I have several problems with this. First of all, using "assert" means
that in "optimizing" mode (python -O) you won't get *any* warnings. I
think that the decision to disable all warnings should be independent
from the decision to "optimize". Second, you're hypergeneralizing the
extended print syntax. Just because I think it's okay to add >>file
to the print syntax doesn't mean that it's now okay to add >>object
syntax to all statements!
I also don't see what warnings have to do with assertions. Assertions
are a mechanism to check for error conditions. What happens if the
error is detected is of less importance -- it could raise an exception
(Python), issue a fatal error (C), or do nothing (in -O mode).
With warnings I believe the issue is not so much the detection of the
condition (for which a regular 'if' statement does just fine) but the
reporting. Again, this is motivated by the fact that I expect that
flexible filtering is essential for a successful warning mechanism.
> "cls" may be any callable object that takes a list as a single
> argument argument list and returns an object with the required
> attributes "get_action" and "format_message"
>
> * get_action() -> "warn"|"error"|"suppress"
> * format_message() -> string
>
> A provided base class implements these methods in a reusable
> fashion. Warning creators are encouraged to merely subclass.
This is just a matter of exposition, but when I first read your PEP I
had a hard time figuring out the purpose of the cls object. It wasn't
until I got to the very end where I saw your example classes that I
realized what it is: it represents a specific warning condition or a
group of related warning conditions.
> This extended form of the assertion statement calls the assertion
> handler code in the new "assertions" module.
I won't harp on this each time, but I'd like to point out once more
that "assertion" is the wrong name for a warning feature. Although it
isn't part of the Zen of Python (by Tim Peters), it should be: a
suggestive name for a feature is worth half a spec!
> The semantics of the built-in assertion handler are defined by the
> following code. It should be exposed in a new "assertions" module.
>
> def handle_assertion(cls, message = ""):
> "This code is called when an assertion fails and cls is not None"
>
> obj = cls(message)
> action = obj.get_action()
>
> if action=="error":
> *** existing assertion code ***
That's just
raise AssertionError, message
Right?
> elif action=="warn":
> sys.stderr.write(obj.format_message())
> elif action=="suppress":
> pass
> else:
> assert action in ["warn","error","suppress"]
>
> Even if handle_assertion is implemented in C, it should be exposed as
> assertions.handle_assertion so that it may be overriden.
Suggestion: have separate warning and error handlers, so that if I
want to override these branches of the if statement I don't have to
repeat the entire handler.
> The generic warning base class is defined below:
>
> class Assertion:
> def __init__(self, *args):
> if len(args) == 1:
> self.args = args[0]
> else:
> self.args = args
>
> def format_message(self):
> sys.stderr.write("%s: %s" %(obj.__name__, self.args))
>
> def get_action(self):
> return (self.get_user_request(self.__class__)
> or sys.default_warning_action)
>
> def get_user_request(self, cls):
> if cls.__name__ in sys.errors:
> return "error"
> elif cls.__name__ in sys.warnings:
> return "warn"
> elif cls.__name__ in sys.disabled_warnings:
I see no definition of sys.disabled_warnings. Instead of
sys.disabled_warnings you meant sys.suppress, right?
> return "suppress"
> for base in cls.__bases__:
> rc = self.get_user_request(base)
> if rc:
> return rc
> else:
> return None
This approach (searching for the class name or the name of one of its
base classes in a list) doesn't look very object-oriented. It would
make more sense to store the desired return value as a class or
instance attribute. The default warning action could be stored on the
base class.
> The Assertion class should be implemented in Python so that it can be
> used as a base class.
>
> Because this code inherently implements "warning state inheritance",
> it would be rare to override any of the methods, but this is possible
> in exceptional circumstances.
>
> Command line
>
> By default the special variables have the following contents:
>
> sys.warnings = []
> sys.errors = []
> sys.suppress = []
> sys.default_warning_action = "warn"
>
> These variables may be changed from the command line. The command line
> arguments are interpreted as described below:
>
> -w XXX => sys.warnings.append("XXX")
> -e XXX => sys.errors.append("XXX")
> -no-w XXX => sys.suppress.append("XXX")
> -wall => sys.default_warning_action => "warn"
> -eall => sys.default_warning_action => "error"
> -no-wall => sys.default_warning_action => "suppress"
Python doesn't support long options (I don't *like* long options so I
doubt that this is a good occasion to start lobbying for them :-). We
can come up with different options though.
> As per the code above, errors take precedence over warnings and
> warnings over suppressions unless a particular assertion class
> specifies otherwise.
I would use a different precedence scheme: a more specific filter
takes precedence over a more general filter. So -eall -wdubious would
mean that "dubious" class warnings are warnings but all others are
errors, and -wall -edubious would mean the opposite.
> Built-in warning objects:
>
> class exception_in_del(Assertion):
> "An exception was ignored in an __del__ method"
>
> class deprecation(Assertion):
> "This feature may be removed in a future version of Python."
>
> class dubious(Assertion):
> "There is a common error associated with this feature."
>
> These class definitions are part of the "Assertion" module. They
> should only ever be used when there exists a way for the programmer to
> accomplish the same thing without triggering the warning. For instance
> the way to suppress the deletion exception is to trap exceptions in
> __del__ methods with a try/except block.
--Guido van Rossum (home page: http://www.python.org/~guido/)