[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


>         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/)