Antoon Pardon wrote:
> On 2006-08-28, Scott David Daniels <[EMAIL PROTECTED]> wrote:
> > Antoon Pardon wrote:
> >> On 2006-08-25, Simon Forman <[EMAIL PROTECTED]> wrote:
> >>> ...
> >>> Generally asserts should be used to "enforce" invariants of your code
> >>> (as opposed to typechecking), or to check certain things while
> >>> debugging.
> >>
> >> I don't understand this argument. Can't type checking be seen as
> >> enforcing a code invariant?
> >>
> > But it is practically never the "right" invariant.
>
> And who decides what is and what is not the "right" invariant?

Um, programmers' common sense.

I can't really describe it well in words, so I'll give you an example.

A long time ago, I wrote a simple interpreter that broke incoming
commands up on whitespace and ran the pieces one by one.  Then, for
reasons that are inconsequential to this post, I decided that instead
of using str.split(), I wanted to keep an index into the command string
and "parse" it using that.

It had been a long time since I had had to do this sort of
array/pointer style processing (thank you python, and Guido, et. al.,
too..) and I wasn't fully sure of myself and my code, so I used a bunch
of assert statement to allow myself to *be* sure.

Here's the relevant code (I added a few line continuations to try to
prevent mail/news formatting problems, but of course YMMV):


def interpret(self, command):
    """
    Given a command string, break it into whitespace-delimited
    commands and execute them one after another. Integers and
    floats are pushed onto the stack.

    This variant of the Interpreter class uses a simple pointer into
    the command line string. It increments the 'pointer' and checks
    against the set of chars in string.whitespace to break up the
    command line.

    It keeps track of the command line and the index so that Words it
    executes (that have a reference to it) can perform manipulations
    on them. This permits, for example, the "chomp" word, which takes
    the next non-whitespace sequence of chars immediately following it
    on the command line, makes a string of it, and puts it on the
    stack.
    """
    #Let's have a pointer or index.
    i = 0

    #Remember our command line
    self.command = command

    #Cache the length.
    l = len(command)

    while 0 <= i < l:

        #Run through chars until we reach the end,
        #or some non-whitespace.
        while (i < l) and (command[i] in self.blankspace): i += 1

        #If we've reached the end we're done.
        if i == l: break

        assert i < l, "If not, the line above should have break'd us."\
                      "We should not have i > l ever."

        #Remember our starting index for this word.
        begin = i

        assert command[begin] not in self.blankspace, "Just making
sure."

        #Force at least one increment of i.
        i += 1

        #Run through until the end or some self.blankspace.
        while (i < l) and (command[i] not in self.blankspace): i += 1

        #At this point, we're either at the end of the command line
        #or we're at a blank char delimiting a preceding word.
        assert (i == l) or \
               ((begin < i < l) and (command[i] in self.blankspace))

        #We've found a word.
        word = command[begin:i]

        #first, try making an integer..
        try:
            n = int(word)
            self.stack.append(n)
        except ValueError:

            #if that didn't work, try making a float..
            try:
                f = float(word)
                self.stack.append(f)
            except ValueError:

                #not a float or integer, eh? Let's try executing it..
                try:
                    #Update our pointer in case the word wants it.
                    self.i = i

                    #try to lookup the command and execute it..
                    self.execute(word)

                    #Update our pointer in case the word changed it.
                    i = self.i
                except:
                    ilog.exception('Error executing "%s"', word)

                    #propagate the Exception "upward"
                    raise


It's not the greatest code I've ever written, in fact it's kind of
naive.  (Nowadays I'd probably use re.finditer()...)  But notice that
the assert statements all act to verify that certain conditions are met
at certain places throughout the loop, and that if the code is
operating correctly, i.e. in the way that the programmer (Hi there!)
intended, that the assertions will always succeed.   *That's* the
"invariant" part.

Notice further that none of the asserts have any side effects, and
especially, that they could all be removed without changing the
operation of the code in any way.


This is the "proper" use of assertions, as I understand it.

(They are of course useful when you're trying to track down odd bugs,
but for a similar reason:  You put them into your code at those places
where you suspect your assumptions about it's workings are incorrect.)

One important point I failed to bring up in my response to the OP is
that assertions *can* go away.  (Running python with the '-O' or '-OO'
switches removes assert statements at the bytecode level, along with
code blocks within the "if __debug__:" "magic" if statement.)  You
should never write code that will work differently or fail if it is
suddenly deprived of it's assert statements one day.


> > You don't usually
> > mean type(x) == int, but rather something like x is a prime between 2
> > and 923.  Or 5< x**4 < 429, or _something_ problem specific.  saying
> > that x must be an int is almost always simultaneously too specific and
> > too general.
>
> There seem to be enough problems that work with ints but not with
> floats. In such a case enforcing that the number you work with
> is indeed an int seems fully appropiate.

If you have a reason to restrict your code to using only ints (perhaps
you're packing them into an array of some sort, or passing them to a C
extension module) then yes, of course it's appropriate.  However, these
reasons seem to me to be much narrower (more narrow?) and more
specialized (specializeder?) than they seem to me to be to you.  And
even in these cases, checking type and refusing to work with anything
but actual ints may still be too much.  Perhaps a simple call to int()
might suffice.

Peace,
~Simon

-- 
http://mail.python.org/mailman/listinfo/python-list

Reply via email to