Range Operation pre-PEP

Alex Martelli aleaxit at yahoo.com
Wed May 9 06:23:56 EDT 2001


"Roman Suzi" <rnd at onego.ru> wrote in message
news:mailman.989396968.4753.python-list at python.org...
    ...
> >One thing that comes to mind is the interpretation
> >of the endpoint. The syntax strongly suggests that the
> >endpoint is inclusive, as you propose. But this is
> >not the most useful meaning in Python.
>
> The reason under Python incl-excl nature is that
> these are intermediate points, needed for slice-operations
> to be natural like in:
>
> a[0:0] = [1,2,3]

That is far from being the only reason!

> There is no reason to bring this into ".." notation, because
> its different from ":" even visuall and it is more naturally
> to use convenient incl-incl ranges.

"convenience" is probably the excuse brought for the worst
pieces of software design today, although the older one of
"optimization" is still going strong.

Upper-bound-excluded ranges are in fact more convenient, and
help avoid off-by-one errors.  Having DIFFERENT behavior
between seq[a:b] and [seq[x] for x in a..b] would, moreover,
be PARTICULARLY horrid.


> >time, range() is used in constructs such as
> >
> >  for i in range(len(mylist)):
>
> These must be eliminated by use of maps, apply, etc -
> by functional style.

s/must/may often/.  Iterating over a range of indices
will remain an important idiom even when a newbie has
fully digested map and apply.  For example, often we
need to operate on items in a sequence depending on
the item that comes immediately before or after.  To
say that the approach of zipping the sequence to its
"shifted" copy "must", or even "should", eliminate the
simple and obvious approach of index-loops smacks of
very substantial hubris.

For example, let's say we need the sequence of all
characters that come right after a '!' in string s.


Zip-approach:

def afterbang1(s):
    result = []
    for previous, thisone in zip(s, s[1:]):
        if previous == '!':
            result.append(thisone)
    return result

versus index-approach:

def afterbang2(s):
    result = []
    for i in range(1,len(s)):
        if s[i-1] == '!':
            result.append(s[i])
    return result

versus keep-state approach:

def afterbang3(s):
    result = []
    previous = s[0]
    for c in s[1:]:
        if previous == '!':
            result.append(c)
        previous = c
    return result

versus "map, apply, etc" approach:

def afterbang4(s):
    return map(lambda x,y: y,
        filter(lambda x,y: x=='!',
            map(None, s[:-1], s[1:])))


Personally, I find afterbang2 clearest, and
therefore best.  If concision is a goal, the
first two approaches can easily be rewritten
as list comprehensions, of course:

Zip-approach with LC:

def afterbang1_LC(s):
    return [ thisone
        for previous, thisone in zip(s, s[1:])
        if previous == '!' ]

versus index-approach with LC:

def afterbang2_LC(s):
    return [ s[i]
        for i in range(1, len(s))
        if s[i-1] == '!' ]

Again, it's a subtle matter of taste between
these two, but I think the second one is better.


I definitely wouldn't mind having a
        for i in 1..len(s)
alternative to use in afterbang2 and 2_LC,
but I'd never consider having to use
        for i in 1..len(s)+1
as even remotely approaching acceptability.


> >The alternative, making the endpoint exclusive,
> >would make the meaning of the .. construct
> >somewhat unintuitive.
>
> I agree.

I don't.


> The fact, that there will be TWO ways for ranges doesn't
> bother me, because the reason to include ".." into
> Python is allowing beginners to appreciate more naturally
> looking code than range(1,101).

Getting beginners used to inclusive-upper-end idioms
and then having them trip over exclusive-upper-end
ones elsewhere later is NOT doing them any favour.


Alex






More information about the Python-list mailing list