Is this a good idea or a waste of time?

S

Simon Forman

Antoon said:
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 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 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 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.

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
 
J

Jonathan Gardner

Simon said:
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.

I politely disagree. Rather than an interface that demands an actual
int, demand something that can be typecast as an int.

For instance:

def needsInt(i):
i = int(i)
... pass i to an internal c function that requires an int ...
# Or better yet, write your internal c function to take any Python
object and cast it into an int.

If you absolutely need a particular type of thing, then cast it into
that thing.
 
A

Antoon Pardon

I politely disagree. Rather than an interface that demands an actual
int, demand something that can be typecast as an int.

For instance:

def needsInt(i):
i = int(i)
... pass i to an internal c function that requires an int ...
# Or better yet, write your internal c function to take any Python
object and cast it into an int.

If you absolutely need a particular type of thing, then cast it into
that thing.

The logical conclusion of this decision would be that one should write
sequence classes as follows:

class Seq(object):

...

def __getitem__(self, index):
index = int(index)
...

def __setitem__(self, index, value):
index = int(index)
...

I don't know about you but I personally think this is overkill.
I would also prefer seq[2.5] to raise an exception instead
of returning seq[2]
 
S

Steve Holden

Antoon said:
That doesn't contradict that in other situations good things can be
done by enforcing specific type or at least limiting to a subset
of specific types.
Indeed it doesn't, but perhaps in future we could leave others to work
out the glaringly obvious for themselves rather than having to
explicitly provide the counterargument to each single response to a posting?

crabbi-ly y'rs - steve
 
A

Antoon Pardon

Indeed it doesn't, but perhaps in future we could leave others to work
out the glaringly obvious for themselves rather than having to
explicitly provide the counterargument to each single response to a posting?

That Gabriel found it necessary to respond with that particular remark
suggest to me it wasn't glaringly obvious to him.

If we leave others to work out for themselves what is glaringly obvious
to us, I think a lot of newbee questions won't get answered.
crabbi-ly y'rs - steve

Sorry if I got on your nerves. But why don't you just skip my
articles if they irritate you or put me in a kill file if
you think of me as irratable in general?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,539
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top