Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

more fun with PEP 276

4 views
Skip to first unread message

James_...@i2.com

unread,
Dec 3, 2001, 7:04:12 PM12/3/01
to

It is readily apparent from the PEP 276 thread that while the author has
tried his best to do the tedious, dirty work of showing the modest benefits
of the modest proposal actually contained in PEP 276 many of those
contributing to the thread, OTOH, have been having quite a jolly good time
offering suggestions for wholesale changes in the area of for-loops,
integer sequences, lists, iterators, etc. Is there any compelling reason
why everyone else should be having all the fun? I think not.

And so, without further adieu, here comes "yet another proposal for
changing the heck out of for-loops".

The thinking goes as follows.

Let's start with Greg Ewing's recent suggestion of writing for-loops as:

for -5 <= i <= 5:
print i

The nice thing about the above is the apparent clarity of intent. And the
fact that all combinations of open and closed intervals are handled nicely.
On the down side we observe that this construct requires new syntax, that
it doesn't work outside of the context of a for-loop (in fact, it is a
relational expression outside the context of a for-loop), and that there is
no apparent mechanism for having a step size other than 1 (or -1).

Now I, for one, happen to like the "for i in iterator:" construct (with
emphasis on the *in*). Also, others have seemed to show fondness for the
Haskell-like style of:

[0,1 ... 10]

(using the suggested existing Python ellipsis notation, i.e., "...").

So what if we turn things around a little and say:

for i in -5 <= ... <= 5:
print i

One little hitch is that Python only supports the ellipsis literal, "...",
in slice notation. So this would require syntax changes. We really prefer
*not* to ask for such, right?

So for now, what if we just used a builtin object, let's call it "span"
(spam's more-respected cousin ;-).

span represents something that knows how to create an iterator for a "span
of numbers" between a given one and another.

So we would now write:

for i in -5 <= span <= 5:
print i

We can make span an instance of a class and then note that "<=", ">=", etc.
are operators that we can implement using the magic methods __le__, __ge__,
etc.

Unfortunately, this won't work very well because of a couple of things.
The comparison magic methods don't have left and right versions the way
arithmetic operators do. So we can't really distinguish increasing
sequences from decreasing sequences like we would want. Worse is that

-5 <= span <= 5

turns into "(-5 <= span) and (span <= 5)" instead of "(-5 <= span) <= 5)".
And we have no control over this. Finally, "-5 <= span <= 5" when used in
an "if" statement should do something boolean and not something
iterator-ish to be consistent with relational expressions in general.

So creating a class that redefines the relational operators doesn't work
out quite as well as one would hope in this situation.

But, if we were willing to be somewhat flexible and non-perfectionistic, we
could try a slight variation on all of this.

Given that some have suggested using [xxx], [xxx), (xxx] as ways of
indicating various combinations of open and closed intervals (to the dismay
of others), the following might not be such a traumatic stretch.

Suppose we use "/" to indicate an open interval and "//" to indicate a
closed interval as in, for example:

-3 // ... // 3 # closed-closed: -3, -2, -1, 0, 1, 2, 3

-3 // ... / 3 # closed-open: -3, -2, -1, 0, 1, 2

-3 / ... // 3 # open-closed: -2, -1, 0, 1, 2, 3

-3 / ... / 3 # open-open: -2, -1, 0, 1, 2

etc.

Let's continue using "span", though, instead of "..." so that we don't
require syntax changes.

Note that "//" and "/" are operators with corresponding magic methods (in
Python 2.2). Further note that they each have left and right versions.

We now create a class, IteratorBounds that holds the start value, stop
value, step value, and "open/closed" status for the left and right sides of
an enumeration of numbers. We make a default instance of IteratorBounds
named "span" with the following default values:

stop == 0
start == 0
step == 1
left == 'closed'
right == 'closed'

Using the example implementation included at the end of this message, we
can write things like:

Python 2.2b1 (#25, Oct 19 2001, 11:44:52) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

>>> for i in -5 // span // 5: # closed-closed
... print i,
...
-5 -4 -3 -2 -1 0 1 2 3 4 5
>>>

>>> for i in -5 / span / 5: # open-open
... print i,
...
-4 -3 -2 -1 0 1 2 3 4
>>>

>>> for i in -5 // span / 5: # closed-open
... print i,
...
-5 -4 -3 -2 -1 0 1 2 3 4
>>>

>>> for i in -5 / span // 5: # open-closed
... print i,
...
-4 -3 -2 -1 0 1 2 3 4 5
>>>


We can handle descending intervals as well as ascending (specified by
reversing the order) as in:

>>> for i in 5 // span // -5: # descending closed-closed
... print i,
...
5 4 3 2 1 0 -1 -2 -3 -4 -5
>>>


We can do shortcuts as in:

>>> for i in span // 5:
... print i,
...
0 1 2 3 4 5
>>>

>>> for i in -5 // span:
... print i,
...
-5 -4 -3 -2 -1 0
>>>


We can also change the step size (using several techniques) as in:

>>> for i in 0 // span(step=2) // 20:
... print i,
...
0 2 4 6 8 10 12 14 16 18 20
>>>

>>> for i in 0 // span.by(2) // 20:
... print i,
...
0 2 4 6 8 10 12 14 16 18 20
>>>


Returning to the motivating example of PEP 276, we can easily index
structures as in:

>>> mylist = [0,1,2,3,4,5,6,7,8,9]
>>>
>>> for i in span / len(mylist):
... print mylist[i],
...
0 1 2 3 4 5 6 7 8 9
>>>

or for those that like to be more explicit:

>>> for i in 0 // span / len(mylist):
... print mylist[i],
...
0 1 2 3 4 5 6 7 8 9
>>>

Other indexing examples:

>>> for i in len(mylist) / span // 0: # access in reverse order
... print mylist[i],
...
9 8 7 6 5 4 3 2 1 0
>>>

>>> for i in len(mylist) / span: # reverse order short form
... print mylist[i],
...
9 8 7 6 5 4 3 2 1 0
>>>

>>> for i in span(step=2) / len(mylist): # access every other item
... print mylist[i],
...
0 2 4 6 8
>>>

>>> for i in span(step=3) / len(mylist): # every third item
... print mylist[i],
...
0 3 6 9
>>>


But wait, there's more ...

This mechanism works outside of for-loops equally well.

>>>
>>> list(0 // span // 9)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> list(span // 9)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> list(-5 // span // 5)
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
>>>
>>> list(-5 / span / 5)
[-4, -3, -2, -1, 0, 1, 2, 3, 4]
>>>
>>> list(5 // span // -5)
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]
>>>
>>> list(5 / span / -5)
[4, 3, 2, 1, 0, -1, -2, -3, -4]
>>>
>>> list(0 // span(step=2) // 20)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
>>>
>>> list(20 / span(step=3) / 0)
[17, 14, 11, 8, 5, 2]
>>>
>>> i = 3
>>> i in 0 // span // 5
1
>>> i in -5 // span // 0
0
>>>


And, if you order now, we'll throw in:

>>>
>>> [1,2,3] + 10 // span // 15 + [21,22,23]
[1, 2, 3, 10, 11, 12, 13, 14, 15, 21, 22, 23]
>>>


But wait, there's even *more*.

>>>
>>> list('a' // span // 'j')
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
>>>
>>> list('a' // span(step=2) // 'z')
['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']
>>>
>>> list('z' // span // 'a')
['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l',
'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
>>>


The (claimed) advantages with this scheme include:
- no syntax changes required (!!!)
- handles all combinations of closed/open intervals
- handles descending as well as ascending intervals
- allows step size to be specified
- reuses the "i in iterator" paradigm of existing for-loops
- supports shortcuts for the common case of indexing from 0 to len-1
- works outside of for-loops ("in" statement, list & tuple functions)
- no confusion with or overloading of list or tuple syntax
- no list versus iterator confusion
- is reasonably transparent (once you get used to it ;-)
- is straightforward to implement

On the down side:
- not as immediately transparent as "-5 <= i <= 5"

Anyway, another nice advantage is that you can take the example
implementation below and play with it before finalizing your opinion (which
I hope you will do :-). Note, the iterator in the example implementation
uses a 2.2 generator, so you need 2.2. (Or you could implement a separate
iterator class and try it in 2.1).

Now, that *was* fun, wasn't it. <wink>

Jim

====================================

from __future__ import generators

import operator


class IteratorBounds:

stopOpDict = {
(1,0):operator.__lt__, # step positive, rightside open
(1,1):operator.__le__, # step positive, rightside closed
(0,0):operator.__gt__, # step negative, leftside open
(0,1):operator.__ge__ # step negative, leftside closed
}

def __init__(self,stop=0,start=0,step=1,left='closed',right='closed'):
self.stop = stop
self.start = start
self.step = step
self.left = left # 'closed' or 'open'
self.right = right # 'closed' or 'open'

def __call__(self,stop=0,start=0,step=1,left='closed',right='closed'):
return IteratorBounds(
stop=stop,
start=start,
step=step,
left=left,
right=right)

def __iter__(self):
start,stop = self.calcStartStop()
if start is None:
raise StopIteration
return
step = self.step
if ((stop > start and step < 0) or
(stop < start and step > 0)):
step = -step
i = start
if self.left == 'open':
i = self.calcNext(i,step)
if i is None:
raise StopIteration
return
stopOp = self.stopOpDict[(step >= 0),(self.right == 'closed')]
while 1:
if not stopOp(i,stop):
break
yield i
i = self.calcNext(i,step)
if i is None:
break
raise StopIteration

def calcStartStop(self):
start = self.start
stop = self.stop
if isinstance(start,str) and not isinstance(stop,str):
try:
stop = chr(stop)
except:
return None,None
elif isinstance(stop,str) and not isinstance(start,str):
try:
start = chr(start)
except:
return None,None
return start,stop

def calcNext(self,obj,step):
if isinstance(obj,str):
try:
return chr(ord(obj)+step)
except:
return
return obj + step

def __div__(self,other):
'''Return an IteratorBounds that is open on the RHS at other'''
result = IteratorBounds(
stop=other,
start=self.start,
step=self.step,
left=self.left,
right='open')
return result

def __floordiv__(self,other):
'''Return an IteratorBounds that is closed on the RHS at other'''
result = IteratorBounds(
stop=other,
start=self.start,
step=self.step,
left=self.left,
right='closed')
return result

def __rdiv__(self,other):
'''Return an IteratorBounds that is open on the LHS at other'''
result = IteratorBounds(
stop=self.stop,
start=other,
step=self.step,
left='open',
right=self.right)
return result

def __rfloordiv__(self,other):
'''Return an IteratorBounds that is closed on the LHS at other'''
result = IteratorBounds(
stop=self.stop,
start=other,
step=self.step,
left='closed',
right=self.right)
return result

def __pow__(self,other):
'''Return an IteratorBounds with step set to other'''
if not isinstance(other,int):
raise TypeError
result = IteratorBounds(
stop=self.stop,
start=self.start,
step=other,
left=self.left,
right=self.right)
return result

def by(self,step):
'''Return an IteratorBounds with step set to step'''
if not isinstance(step,int):
raise TypeError
result = IteratorBounds(
stop=self.stop,
start=self.start,
step = step,
left = self.left,
right = self.right)
return result

def __add__(self,other):
'''Create a list on self and add to other'''
if isinstance(other,list):
return list(self) + other
raise TypeError

def __radd__(self,other):
'''Create a list on self and add to other'''
if isinstance(other,list):
return other + list(self)
raise TypeError


span = IteratorBounds() # Default instance


#$ Testing

def test(testItem):
result = list(eval(testItem[0])) == testItem[1]
print testItem[0], ' passed:', result

def runtests():
testList = [
('-5 // span // 5', [-5,-4,-3,-2,-1,0,1,2,3,4,5]),
('-5 / span / 5', [-4,-3,-2,-1,0,1,2,3,4]),
('-5 // span / 5', [-5,-4,-3,-2,-1,0,1,2,3,4]),
('-5 / span // 5', [-4,-3,-2,-1,0,1,2,3,4,5]),
('5 // span // -5', [5,4,3,2,1,0,-1,-2,-3,-4,-5]),
('5 / span / -5', [4,3,2,1,0,-1,-2,-3,-4]),
('5 // span / -5', [5,4,3,2,1,0,-1,-2,-3,-4]),
('5 / span // -5', [4,3,2,1,0,-1,-2,-3,-4,-5]),
('-5 // span.by(2) // 5', [-5,-3,-1,1,3,5]),
('-5 // span(step=2) // 5', [-5,-3,-1,1,3,5]),
('-5 // span **2 // 5', [-5,-3,-1,1,3,5]),
('5 // span.by(-2) // -5', [5,3,1,-1,-3,-5]),
('5 // span(step=-2) // -5', [5,3,1,-1,-3,-5]),
('5 // span **-2 // -5', [5,3,1,-1,-3,-5]),
('span // 5', [0,1,2,3,4,5]),
('span / 5', [0,1,2,3,4]),
('-5 // span', [-5,-4,-3,-2,-1,0]),
('-5 / span', [-4,-3,-2,-1,0]),
("'a' // span // 'd'", ['a','b','c','d']),
("'a' / span / 'd'", ['b','c']),
("'z' // span // 'w'", ['z','y','x','w']),
("'z' / span / 'w'", ['y','x']),
("'a' // span.by(2) // 'j'", ['a','c','e','g','i']),
("'z' / span.by(2) / 'p'", ['x','v','t','r'])
]
for testItem in testList:
test(testItem)

David Eppstein

unread,
Dec 3, 2001, 10:03:57 PM12/3/01
to
In article <mailman.1007424313...@python.org>,
James_...@i2.com wrote:

> Let's start with Greg Ewing's recent suggestion of writing for-loops as:
>
> for -5 <= i <= 5:
> print i
>
> The nice thing about the above is the apparent clarity of intent. And the
> fact that all combinations of open and closed intervals are handled nicely.
> On the down side we observe that this construct requires new syntax, that
> it doesn't work outside of the context of a for-loop (in fact, it is a
> relational expression outside the context of a for-loop), and that there is
> no apparent mechanism for having a step size other than 1 (or -1).

But there isn't a huge distinction between "works only in a for loop" and
"produces general-purpose iterators" because of list comprehensions:
L = [i for -5 <= i <= 5]
--
David Eppstein UC Irvine Dept. of Information & Computer Science
epps...@ics.uci.edu http://www.ics.uci.edu/~eppstein/

Cromwell, Jeremy

unread,
Dec 4, 2001, 2:57:08 PM12/4/01
to
James,
You left out the best part! By adding a little snippet to all the div
routines, you can solve the original problem (having an index that spans a
list.)

try:
other = len(other)
except:
pass

Now you can write:

for i in span/table.rowcount:
for j in span/table.colcount:
print table.value(i,j)


Thanks to your hard work, we can play with this implementation and come up
with some new ideas (maybe we'll even come up with a good one!)

For example by adding different operators:
__lshift__, __rrshift__ = __div__, __rdiv__
__rshift__, __rlshift__ = __floordiv__, __rfloordiv__
and setting:
__ = span # in the spirit of ...

then

>>> list(3>>__>>9)
[4, 5, 6, 7, 8, 9]
>>> list(3>>__<<9)
[4, 5, 6, 7, 8]
>>> list(3<<__<<9)
[3, 4, 5, 6, 7, 8]
>>> list(3<<__>>9)
[3, 4, 5, 6, 7, 8, 9]

which allows

for i in __<< table.rowcount:
for j in __<< table.colcount:
print table.value(i,j)

Jeremy Cromwell
#############################################
"Heaven goes by favor, If it went by merit, you would stay out and your
dog would go in."
--Mark Twain


-----Original Message-----
From: James_...@i2.com [mailto:James_...@i2.com]
Sent: Monday, December 03, 2001 4:04 PM
To: pytho...@python.org
Subject: more fun with PEP 276

It is readily apparent from the PEP 276 thread that while the author has
tried his best to do the tedious, dirty work of showing the modest benefits
of the modest proposal actually contained in PEP 276 many of those
contributing to the thread, OTOH, have been having quite a jolly good time
offering suggestions for wholesale changes in the area of for-loops,
integer sequences, lists, iterators, etc. Is there any compelling reason
why everyone else should be having all the fun? I think not.

And so, without further adieu, here comes "yet another proposal for
changing the heck out of for-loops".

The thinking goes as follows.

Let's start with Greg Ewing's recent suggestion of writing for-loops as:

for -5 <= i <= 5:
print i

The nice thing about the above is the apparent clarity of intent. And the
fact that all combinations of open and closed intervals are handled nicely.
On the down side we observe that this construct requires new syntax, that
it doesn't work outside of the context of a for-loop (in fact, it is a
relational expression outside the context of a for-loop), and that there is
no apparent mechanism for having a step size other than 1 (or -1).

Now I, for one, happen to like the "for i in iterator:" construct (with

[0,1 ... 10]

etc.

Other indexing examples:

Jim

====================================

from __future__ import generators

import operator


class IteratorBounds:


#$ Testing

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

Cromwell, Jeremy

unread,
Dec 4, 2001, 3:06:05 PM12/4/01
to
Oops, the rowcount example already worked with what James wrote. My
addition is for supporting sequence index spanning.

Jeff Shannon

unread,
Dec 4, 2001, 5:27:43 PM12/4/01
to

James_...@i2.com wrote:

> The (claimed) advantages with this scheme include:
> - no syntax changes required (!!!)
> - handles all combinations of closed/open intervals
> - handles descending as well as ascending intervals
> - allows step size to be specified
> - reuses the "i in iterator" paradigm of existing for-loops
> - supports shortcuts for the common case of indexing from 0 to len-1
> - works outside of for-loops ("in" statement, list & tuple functions)
> - no confusion with or overloading of list or tuple syntax
> - no list versus iterator confusion
> - is reasonably transparent (once you get used to it ;-)
> - is straightforward to implement
>
> On the down side:
> - not as immediately transparent as "-5 <= i <= 5"
>

Nor is it immediately more transparent, or shorter, or in much of any other way
(that I can see) preferable to "range(10)"

for i in 0 / span / len(mylist):
#or even..
for i in span/len(mylist):

versus

for i in range(len(mylist):

Hmmm... what's the advantage again? Seems like a lot of work for not much
difference....

Jeff Shannon
Technician/Programmer
Credit International

James_...@i2.com

unread,
Dec 5, 2001, 2:43:07 PM12/5/01
to

Jeremy Cromwell wrote:
>James,
<snip>

>Thanks to your hard work, we can play with this implementation and come up
>with some new ideas (maybe we'll even come up with a good one!)
>
>For example by adding different operators:
> __lshift__, __rrshift__ = __div__, __rdiv__
> __rshift__, __rlshift__ = __floordiv__, __rfloordiv__
>and setting:
> __ = span # in the spirit of ...
>
>then
>
> >>> list(3>>__>>9)
> [4, 5, 6, 7, 8, 9]
> >>> list(3>>__<<9)
> [4, 5, 6, 7, 8]
> >>> list(3<<__<<9)
> [3, 4, 5, 6, 7, 8]
> >>> list(3<<__>>9)
> [3, 4, 5, 6, 7, 8, 9]
>
> which allows
>
> for i in __<< table.rowcount:
> for j in __<< table.colcount:
> print table.value(i,j)
>

Thanks very much for the compliment and added ideas.

It would be nice if there were a way to use relational operators (or
something resembling them as you suggest with << and >>) instead of / and
//. The magic method machinery doesn't seem to have quite enough power to
enable this. But perhaps something could be done by way of enhancements to
make it possible.

Your suggestion of using "__" instead of span or "..." could work. I don't
know how hard it would be to make "..." legal in a general expression (as
in -5 / ... / 5). But "__" works out of the box. :-)

Jim


James_...@i2.com

unread,
Dec 5, 2001, 2:29:39 PM12/5/01
to

To match

for i in span / len(mylist):

more closely you would use xrange instead of range since range actualizes
the list before iterating the values (which, of course, might not matter in
many cases). So the better comparison is to

for i in xrange(len(mylist)):

Although the above is fine, there are a couple of things one can point out:

- xrange is not an ideal name for something used in such a common idiom.
- a nested function is not ideal for such a common idiom.
- the fact that one has the option of using either range or xrange causes
enough confusion that one observes inquiries and debates from time to time
(on this mailing list, for example) about best/proper usage of each.
- xrange and range are both oriented to intervals that are closed on the
left and open on the right. If you want to specify an interval that is
open on the left and closed on the right, then you have to do extra
calculations that can be error-prone, e.g.,

left = getLeftSide()
right = getRightSide()

for i in xrange(left+1,right+1): # open on left, closed on right --
not so obvious

Compared to:

for i in left / span // right: # open on left, closed on right

Furthermore, there is no syntactic mechanism in xrange/range that visually
indicates the fact that the left side is closed and the right is open. And
for any other combination (e.g., open on the left, closed on the right)
xrange and range aren't very obvious.

And using span -- in my experience with this exercise -- didn't seem like
"a lot of work". To me, at least, it seemed quite easy.

So I would suggest that one key difference between span and xrange is that
span has a better mechanism for specifying all combinations of
"open/closed"-ness for intervals whereas xrange and range really target
"closed on left / open on right" intervals (which, granted, are very common
-- but not what one needs for all occasions).

Jim


James_...@i2.com

unread,
Dec 5, 2001, 3:08:41 PM12/5/01
to

David Eppstein wrote:
>But there isn't a huge distinction between "works only in a for loop" and
>"produces general-purpose iterators" because of list comprehensions:
> L = [i for -5 <= i <= 5]

Agreed. Still

tuple([i for -5 <= i <= 5]) # actualize list first, then convert.
extra syntax, too

isn't quite as nice as

tuple(-5 <= ... <= 5)

Or if I need to pass an interval around I would like to be able to say:

myObject.setInterval(left < ... <= right) # I really want an interval
not an actualized list

instead of having to resort back to:

myObject.setInterval(xrange(left+1,right+1) # left < i <= right
doesn't help me here

Plus, "-5 <= i <= 5" doesn't provide a step value (other than 1 and -1 --
important to some, not important to others).

And, to me, the following is reasonably clear pseudo-code:

for i in n > ... >= 0:
for j in i < ... <= n:
if i == j-1:
V[i,j] = id
else:
V[i,j] = min([q(i,k,j) @ V[i,j] @ V[j,k] for k in i < ... < j])


So I think it would be nearly ideal if one could implement

-5 <= ... <= 5

instead of

-5 // span // 5

but with the same functionality.

Jim


Jeff Shannon

unread,
Dec 6, 2001, 1:49:07 PM12/6/01
to

James_...@i2.com wrote:

> Jeff Shannon wrote:
>
> >Hmmm... what's the advantage again? Seems like a lot of work
> >for not much difference....
>
> To match
>
> for i in span / len(mylist):
>
> more closely you would use xrange instead of range since range actualizes
> the list before iterating the values (which, of course, might not matter in
> many cases).

Indeed, shouldn't matter in the vast majority of cases (IIRC, xrange() doesn't
become advantageous until the interval-size is in the thousands, or higher, and
most loops seem to be *much* smaller than that). As a rule, I always use
range() unless I *know* that I'm creating a huge list.


> So the better comparison is to
>
> for i in xrange(len(mylist)):
>
> Although the above is fine, there are a couple of things one can point out:
>
> - xrange is not an ideal name for something used in such a common idiom.

> - a nested function is not ideal for such a common idiom.

Overloading the division operator is an even *less* ideal solution for such a
common idiom. And since 99% of cases are handled just as effectively by
range(), the (putative) awkwardness of the name xrange is not that big of a
deal.


> - the fact that one has the option of using either range or xrange causes
> enough confusion that one observes inquiries and debates from time to time
> (on this mailing list, for example) about best/proper usage of each.

One sees inquiries and debates about the proper usage of every language
feature; this one seems pretty easy to clear up, and doesn't seem to cause any
horrible confusion. (Indeed, in most cases, the practical difference is
trivial.)


> - xrange and range are both oriented to intervals that are closed on the
> left and open on the right. If you want to specify an interval that is
> open on the left and closed on the right, then you have to do extra
> calculations that can be error-prone, e.g.,

If you *always* have half-open intervals (in the same direction), then you will
know, almost without thinking about it, which way any corrections have to be
made. If you sometimes have half-open, sometimes closed, sometimes
reversed-half-open, sometimes fully open, etc... then you will constantly be
having to check what you're doing in *this* case, look back at what you've
done, think through in detail what the effects are, etc, etc. (There should be
one, and preferably *only* one, obvious way to do it.)


> left = getLeftSide()
> right = getRightSide()
>
> for i in xrange(left+1,right+1): # open on left, closed on right --
> not so obvious

In most cases, I'd expect the underlying source of the data to behave according
to 0-based half-open intervals. If it doesn't, I'd correct it at the
interface, instead of in the range()--

left = getLeftSide() - 1
right = getRightSide() - 1
for i in range(left, right): ...

Not perfect, perhaps, but at least explicit.


> Compared to:
>
> for i in left / span // right: # open on left, closed on right

This looks far less clear to me, sorry. Was // supposed to be open, or
closed? Let me search through my reference book again....


> Furthermore, there is no syntactic mechanism in xrange/range that visually
> indicates the fact that the left side is closed and the right is open. And
> for any other combination (e.g., open on the left, closed on the right)
> xrange and range aren't very obvious.

They are *always* the same, therefore no visual mechanism is needed. The fact
that it says "range" tells me that it's closed left and open right. I don't
have to worry about any other possibility.


> And using span -- in my experience with this exercise -- didn't seem like
> "a lot of work". To me, at least, it seemed quite easy.

Using, perhaps not. Implementing it sure looked like it. Yeah, it only has to
be done once... and then maintained... and then possibly updated with each new
version of Python... etc, etc, etc... All for a way to make range() look
different (it looks *more* confusing to me, though obviously not to you).


> So I would suggest that one key difference between span and xrange is that
> span has a better mechanism for specifying all combinations of
> "open/closed"-ness for intervals whereas xrange and range really target
> "closed on left / open on right" intervals (which, granted, are very common
> -- but not what one needs for all occasions).

This is a recipe for confusion. If you can't rely on the half-open interval
being always the same, then it *will* bite you eventually, and a lot worse than
people currently get bit by range()'s behavior (which is at least predictable).

James_...@i2.com

unread,
Dec 6, 2001, 4:57:39 PM12/6/01
to

Jeff Shannon wrote:
<snip>

>If you *always* have half-open intervals (in the same direction), then you
will
>know, almost without thinking about it, which way any corrections have to
be
>made. If you sometimes have half-open, sometimes closed, sometimes
>reversed-half-open, sometimes fully open, etc... then you will constantly
be
>having to check what you're doing in *this* case, look back at what you've
>done, think through in detail what the effects are, etc, etc. (There
should be
>one, and preferably *only* one, obvious way to do it.)
<snip>

>In most cases, I'd expect the underlying source of the data to behave
according
>to 0-based half-open intervals. If it doesn't, I'd correct it at the
>interface, instead of in the range()--
>
>left = getLeftSide() - 1
>right = getRightSide() - 1
>for i in range(left, right): ...
>
>Not perfect, perhaps, but at least explicit.
<snip>

>They are *always* the same, therefore no visual mechanism is needed. The
fact
>that it says "range" tells me that it's closed left and open right. I
don't
>have to worry about any other possibility.
<snip>

>This is a recipe for confusion. If you can't rely on the half-open
interval
>being always the same, then it *will* bite you eventually, and a lot worse
than
>people currently get bit by range()'s behavior (which is at least
predictable).

Yes, "closed on the left, open on the right" intervals are very common. In
fact, that is what helped motivate PEP 276 -- making it *really* easy to
deal with such intervals when accessing items in a structure by index:

for i in table.rowcount:
for j in table.colcount:
print table.value(i,j)

Here, i and j both use "closed on the left, open on the right" intervals (0
to "len"-1).

But "very common" is not "always" and some of the posters in the PEP 276
thread seemed to be interested in making it more convenient and *explicit*
when one deals with other types of intervals. As in, for example, David
Eppstein's:

for n > i >= 0:
for i < j <= n:


if i == j-1:
V[i,j] = id
else:

V[i,j] = min([q(i,k,j) @ V[i,j] @ V[j,k] for i < k < j])

or the equivalent:

for i in n > ... >= 0:
for j in i < ... <= n:
if i == j-1:
V[i,j] = id
else:
V[i,j] = min([q(i,k,j) @ V[i,j] @ V[j,k] for k in i < ... < j])


I believe that many Python users think it would be good to have some
mechanism that is *more explicit* than range/xrange when dealing with
intervals. I don't think

-5 // span // 5

is the ideal syntax. I think it would be much better if one could write:

-5 <= ... <= 5

I would like to see a class Interval (named IteratorBounds in my posted
example implementation) that contains start, stop, step, left, and right
values where you could create interval instances using the syntax of
relational operators. Such intervals could be specified in any context
where expressions are valid and would yield iterators in for-loops and
other places where iterators are called for. Python, as is, doesn't
support this (unless I'm missing something). So I showed how this could be
approximated with the -5 // span // 5 equivalent. I found it very handy
when playing around with it. Again, if -5 <= ... <= 5 could be made to
work, it would be *really* nice (in my view).

Even for "closed on the left, open on the right" intervals I would prefer:

for i in ... < table.rowcount:
for j in ... < table.colcount:
print table.value(i,j)

over:

for i in xrange(table.rowcount): # yes, I have a *lot* of rows
for j in range(table.colcount):
print table.value(i,j)


Jim

William Tanksley Google

unread,
Dec 10, 2001, 4:05:37 PM12/10/01
to
James_...@i2.com wrote:

> I think not.

A compelling argument. <wink>

> >>> for i in -5 // span // 5: # closed-closed
> ... print i,

> -5 -4 -3 -2 -1 0 1 2 3 4 5

Intriguing. Odd. Perverted. Words would fail me, but doggone it, I
find myself liking it.

> >>> mylist = [0,1,2,3,4,5,6,7,8,9]
> >>> for i in span / len(mylist):
> ... print mylist[i],

> 0 1 2 3 4 5 6 7 8 9

The scary thing is that despite the ENORMOUS gap between the purpose
of the / operator and your abuse of it (help! I'm being repressed!), I
can still read this naturally: "span over length of myList".

> The (claimed) advantages with this scheme include:
> - no syntax changes required (!!!)
> - handles all combinations of closed/open intervals

Minor niggle: how would users remember which operator represents which
type of range ending? Do you have a proposed mnemonic?

> Now, that *was* fun, wasn't it. <wink>

Actually, it was. I dunno if this should go in, but for its purposes
I like it more than any of the other ideas, including my own.

> Jim

-Billy

James_...@i2.com

unread,
Dec 10, 2001, 7:04:04 PM12/10/01
to

wtanksley wrote:
>> >>> for i in -5 // span // 5: # closed-closed
>> ... print i,
>> -5 -4 -3 -2 -1 0 1 2 3 4 5
>
>Intriguing. Odd. Perverted. Words would fail me, but doggone
>it, I find myself liking it.

Thanks, Billy. I was starting to wonder if
*anyone* was going come around to appreciating
its quirky wholesome-ness. <wink>

>The scary thing is that despite the ENORMOUS gap between the purpose
>of the / operator and your abuse of it (help! I'm being repressed!), I
>can still read this naturally: "span over length of myList".
>

>Minor niggle: how would users remember which operator represents which
>type of range ending? Do you have a proposed mnemonic?

/ and // weren't my first choice (< and <= were -- I'm still working
on that approach) but since they had the advantage of actually
*working* in this scheme, I just went with it. In my mind
/ seems "thinner" than // so it seems better matched to the
"thinner" interior interval whereas // seems better matched
to the *thicker* inclusive interval. Also, / seems better
matched to < (>) and // seems better matched to <= (>=).

>Actually, it was. I dunno if this should go in, but for its purposes
>I like it more than any of the other ideas, including my own.

Hey, thanks again.

Jim


Nick Mathewson

unread,
Dec 10, 2001, 9:12:23 PM12/10/01
to
In article <mailman.1008029196...@python.org>,
James_...@i2.com wrote:
[...]

> / and // weren't my first choice (< and <= were -- I'm still working
> on that approach) but since they had the advantage of actually
> *working* in this scheme, I just went with it.

Did you see my post under the subject heading of
"An implementation of a variation on "for 1 <= i <= 10" ?

I believe I managed to get "for i in 1 <= Integers() <= 10" nailed,
and I _think_ I got "for i in 1 <= ints <= 10" down pretty well too.

I'll repeat the information here:

============================================================

Here's some code that implements almost this behavior. Now you can type:

for i in 1 <= ints <= 10:
print i

I don't have "for i in ints <= 10" going yet, but that should be a fairly
easy extension.

This code requires that you have at least Python 2.2 for the iterator
logic to work. Python2.2 is still in beta, but hey -- so is this
code. :)

#!/usr/bin/python

class Integers:
def __init__(self, step=None, auto=0):
self.lb = None #Lower bound (inclusive)
self.ub = None #Upper bound (inclusive)
self.up = None #Flag: Are we counting up (a<i<c) or down (a>i>c)?
self.step = step #Stepping increment
self.auto = auto #Automatically return an iterator when we have 2
# bounds?

def __gt__(self, n):
self.lb = n+1
if self.ub == None: self.up = 1
if self.auto and self.ub != None and self.lb != None:
return iter(self)
else:
return self

def __lt__(self, n):
self.ub = n-1
if self.lb == None: self.up = 0
if self.auto and self.ub != None and self.lb != None:
return iter(self)
else:
return self

def __ge__(self, n):
self.lb=n
if self.ub == None: self.up = 1
if self.auto and self.ub != None and self.lb != None:
return iter(self)
else:
return self

def __le__(self, n):
self.ub=n
if self.lb == None: self.up = 0
if self.auto and self.ub != None and self.lb != None:
return iter(self)
else:
return self

def __nonzero__(self):
return 1

def __iter__(self):
if self.step == None:
self.step = (-1, 1)[self.up]
else:
assert self.step != 0 and self.up == (self.step > 0)

if self.up:
return iter(xrange(self.lb, self.ub+1, self.step))
else:
return iter(xrange(self.ub, self.lb-1, self.step))

ints = Integers(step=1, auto=1)

if __name__=='__main__':

assert [i for i in 1 <= Integers() <= 10] == range(1,11)
assert [i for i in 10 >= Integers() >= 1] == range(10,0,-1)

assert [i for i in 1 < Integers() < 10] == range(2,10)
assert [i for i in 10 > Integers() > 1] == range(9,1,-1)

assert [i for i in 1 < Integers() <= 10] == range(2,11)
assert [i for i in 10 > Integers() >= 1] == range(9,0,-1)

assert [i for i in 1 <= Integers() < 10] == range(1,10)
assert [i for i in 10 >= Integers() > 1] == range(10,1,-1)

assert [i for i in 2 <= Integers(step=2) < 99] == range(2,99,2)

n = 0
for i in 1 <= ints <= 10:
for j in 1 <= ints <= 10:
n += 1
assert n == 100

assert zip(1 <= ints <= 3, 1 <= ints <= 3) == zip((1,2,3), (1,2,3))
============================================================

Yrs,
--
Nick Mathewson <Q nick Q m at alum dot mit dot edu>
Remove Q's to respond. No spam.

James_...@i2.com

unread,
Dec 11, 2001, 1:29:00 PM12/11/01
to

Nick Mathewson

>James_...@i2.com wrote:
>
>> / and // weren't my first choice (< and <= were -- I'm still working
>> on that approach) but since they had the advantage of actually
>> *working* in this scheme, I just went with it.
>
>Did you see my post under the subject heading of
> "An implementation of a variation on "for 1 <= i <= 10" ?

Yes, I saw your original post -- and very nice work! Good job.

I thought I spotted some difficulties with the shortcuts that were left
out: the one-sided relations. I've been working on a variant that tries to
get the one-sided relations to work the way I want and adds a few twists of
its own.

I'll try to post it soon and you can have a look.

Jim


phil hunt

unread,
Dec 12, 2001, 11:32:46 AM12/12/01
to
On Mon, 10 Dec 2001 16:04:04 -0800, James_...@i2.com <James_...@i2.com> wrote:
>
>wtanksley wrote:
>>> >>> for i in -5 // span // 5: # closed-closed
>>> ... print i,
>>> -5 -4 -3 -2 -1 0 1 2 3 4 5
>>
>>Intriguing. Odd. Perverted. Words would fail me, but doggone
>>it, I find myself liking it.
>
>Thanks, Billy. I was starting to wonder if
>*anyone* was going come around to appreciating
>its quirky wholesome-ness. <wink>
>
>>The scary thing is that despite the ENORMOUS gap between the purpose
>>of the / operator and your abuse of it (help! I'm being repressed!), I
>>can still read this naturally: "span over length of myList".
>>
>>Minor niggle: how would users remember which operator represents which
>>type of range ending? Do you have a proposed mnemonic?

how about:

for i in -5 to 5:

or

for i in -5 to 5 step 2:


Here, "to" and "to...step" are keywords that create an xrange with the
required values; when used deirecvtly in a for loop, the compiler could
presumably optimise that out, but this should still work:

values = -5 to 5 step 3
for i in values:
print i

should result in:
-5
-2
1
4

--
*** Philip Hunt *** ph...@comuno.freeserve.co.uk ***

David Eppstein

unread,
Dec 12, 2001, 12:12:48 PM12/12/01
to
In article <slrna1f1le...@comuno.freeserve.co.uk>,
ph...@comuno.freeserve.co.uk (phil hunt) wrote:

> how about:
>
> for i in -5 to 5:
>
> or
>
> for i in -5 to 5 step 2:
>
>
> Here, "to" and "to...step" are keywords that create an xrange with the
> required values

Not bad, but seems to force closed intervals only, which is somewhat at
odds with other features of Python.

Tony J Ibbs (Tibs)

unread,
Dec 12, 2001, 12:02:42 PM12/12/01
to
The attributions have gotten tangled, but trying to reconstruct them, I
*think* someone called Billy (being quoted by wtanksley) may have
written:

> >>Minor niggle: how would users remember which operator
> >>represents which type of range ending? Do you have a
> >>proposed mnemonic?

to which phil hunt responded:


> how about:
>
> for i in -5 to 5:
>
> or
>
> for i in -5 to 5 step 2:

Nah - that still doesn't resolve the "open or closed" end problem.

For American English speakers, I suspect that::

for i in -5 through 5:

and::

for i in -5 to 5:

may work (did I get that right?), but for British English speakers those
wouldn't work terribly well (even despite the gradual merging of
languages), since that distinctive use of "through" is not common. Maybe
the only sensible solution would be something like::

for i in -5 uptobutnotincluding 5:

and::

for i in -5 uptoandnotforgetting 5:

On the other hand, maybe the problem with finding a "spelling" for the
issue that actually works for more than a handful of people at a time is
why this issue doesn't get addressed?

(mind you, *I* thought the ``for -5 < i <= 3:``
proposal (sorry if I got it wrong) was meant as
a neat piece of humour when I first read it, so
who am I to comment?)

Tibs

--
Tony J Ibbs (Tibs) http://www.tibsnjoan.co.uk/
I hope that's my tongue in my cheek and not my foot in my mouth...
My views! Mine! Mine! (Unless Laser-Scan ask nicely to borrow them.)


Courageous

unread,
Dec 12, 2001, 1:09:36 PM12/12/01
to

>> > for i in -5 to 5:

The limited number of Linguistic Forms in Python is an
essential element of the Programmers Linguistic Weapons
Treaty of 1980. Breaking the Treaty would certainly lead
to a new Linguistic Forms Race. This could lead to a
situation where a massive exchange of Linguistic Forms
occurs, causing a chain reaction whereby the Mainframe
is Shutdown until a new species of programs arises to rule
the Mainframe. Perhaps it would look like Lisp.

C//

phil hunt

unread,
Dec 12, 2001, 2:26:29 PM12/12/01
to
On Wed, 12 Dec 2001 09:12:48 -0800, David Eppstein <epps...@ics.uci.edu> wrote:
>In article <slrna1f1le...@comuno.freeserve.co.uk>,
> ph...@comuno.freeserve.co.uk (phil hunt) wrote:
>
>> how about:
>>
>> for i in -5 to 5:
>>
>> or
>>
>> for i in -5 to 5 step 2:
>>
>>
>> Here, "to" and "to...step" are keywords that create an xrange with the
>> required values
>
>Not bad, but seems to force closed intervals only, which is somewhat at
>odds with other features of Python.

Another possibility would be to make .. part of the syntax for lists, i.e.:

a = [ -5 .. 5, 10, 20 ]

a is [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 10, 20]

Of course, these features can be implemented in existing python, for
example using functions fromTo() and fromToStep() for the 1st usage,
and something like this for the second:

a = buildList(range(-5, 5), 10, 20)

etc.

IOW any new feature would be mere syntactic sugar that would have the
deficiency of making the compiler more complex.

Maybe instead we should be looking at implementing a clever macro system
as a preprocessor to Python. Something that could cope with ...to...step...
would have to be quite complex, but a macro system that used function-call
like syntax would be simpler.

Delaney, Timothy

unread,
Dec 12, 2001, 9:29:23 PM12/12/01
to
> From: ph...@comuno.freeserve.co.uk
>
> Another possibility would be to make .. part of the syntax
> for lists, i.e.:
>
> a = [ -5 .. 5, 10, 20 ]
>
> a is [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 10, 20]
>
> Maybe instead we should be looking at implementing a clever
> macro system
> as a preprocessor to Python. Something that could cope with
> ...to...step...
> would have to be quite complex, but a macro system that used
> function-call
> like syntax would be simpler.

And we come full circle ... again ...

Tim Delaney

Duncan Booth

unread,
Dec 13, 2001, 3:45:35 AM12/13/01
to
"Tony J Ibbs (Tibs)" <to...@lsl.co.uk> wrote in
news:mailman.100817659...@python.org:

> For American English speakers, I suspect that::
>
> for i in -5 through 5:
>
> and::
>
> for i in -5 to 5:
>
> may work (did I get that right?), but for British English speakers those
> wouldn't work terribly well (even despite the gradual merging of
> languages), since that distinctive use of "through" is not common.

Don't the Americans spell it 'thru'?

--
Duncan Booth dun...@rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?

0 new messages