# more fun with PEP 276

Cromwell, Jeremy jcromwell at ciena.com
Tue Dec 4 21:06:05 CET 2001

```Oops, the rowcount example already worked with what James wrote.  My
addition is for supporting sequence index spanning.

-----Original Message-----
From: Cromwell, Jeremy
Sent: Tuesday, December 04, 2001 11:57 AM
To: python-list at python.org
Subject: RE: more fun with PEP 276

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_Althoff at i2.com [mailto:James_Althoff at i2.com]
Sent: Monday, December 03, 2001 4:04 PM
To: python-list at 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.

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

[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
>>>

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

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

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

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

```