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.

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)



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




More information about the Python-list mailing list