[Python-ideas] Pass a function as the argument "step" of range()

Pierre Quentel pierre.quentel at gmail.com
Thu Jul 2 22:20:16 CEST 2015


@Steven, Mark
The definition of range() in Python docs says :

Python 2.7 : "This is a versatile function to create lists containing
arithmetic progressions. It is most often used in for loops."

Python 3.4 : "The range type represents an immutable sequence of numbers
and is commonly used for looping a specific number of times in for loops."

Both stress that range is most often used in a for loop (it doesn't
"happens to sometimes be used" in for loops, and is rarely used for
membership testing). Python 2.7 limited its definition to arithmetic
progressions, but Python 3.4 has a more general definition (an immutable
sequence of numbers). I really don't think that the proposal would change
the general idea behind range : a suite of integers, where each item is
built from the previous following a specific pattern, and stopping when a
"stop" value is reached.

@Terry

If the argument "step" is an integer, all the algorithms used in the
mentioned methods would remain the same, so performance would not be
affected for existing code.

If the argument is a function, you are right, the object returned can't
support some of these methods, or with an excessive performance penalty ;
it would support __iter__ and not much more.

I agree that this is a blocking issue : as far as I know all Python
built-in functions return objects of a given type, regardless of its
arguments.

Thank you all for your time.
Pierre

2015-07-02 21:53 GMT+02:00 Terry Reedy <tjreedy at udel.edu>:

> On 7/2/2015 2:30 AM, Pierre Quentel wrote:
>
>> In languages such as Javascript, the incrementation of a for loop
>> counter can be done by an operation, for instance :
>>
>> for(i=1; i<N; i*=2)
>>
>> would iterate on the powers of 2 lesser than N.
>>
>> To achieve the same thing in Python we currently can't use range()
>> because it increments by an integer (the argument "step"). An option is
>> to build a generator like :
>>
>> def gen(N):
>>      i = 1
>>      while i<=N:
>>          yield i
>>          i *= 2
>>
>> then we can iterate on gen(N).
>>
>> My proposal is that besides an integer, range() would accept a function
>> as the "step" argument, taking the current counter as its argument and
>> returning the new counter value. Here is a basic pure-Python
>> implementation :
>>
>> import operator
>>
>> class Range:
>>
>>      def __init__(self, start, stop, incrementor):
>>          self.start, self.stop = start, stop
>>          self.incrementor = incrementor
>>          # Function to compare current counter and stop value : <= or >=
>>          self.comp = operator.ge <http://operator.ge> if
>> self.stop>self.start else operator.le
>>          self.counter = None
>>
>>      def __iter__(self):
>>          return self
>>
>>      def __next__(self):
>>          if self.counter is None:
>>              self.counter = self.start
>>          else:
>>              self.counter = self.incrementor(self.counter)
>>          if self.comp(self.counter, self.stop):
>>              raise StopIteration
>>          return self.counter
>>
>
> The idea of iterating by non-constant steps is valid.  Others have given
> multiple options for doing so.
>
> The idea of stuffing this into range is not valid.  It does not fit into
> what 3.x range actually is.  The Range class above is a one-use iterator.
> This post and your counter-responses seem to miss what others have alluded
> to.  3.x range class is something quite different -- a reusable
> collections.abc.Sequence class, with a separate range_iterator class.
>
> The range class has the following sequence methods with efficient O(1)
> implementations: __contains__, __getitem__, __iter__, __len__,
> __reversed__, count, and index methods.  Having such efficient methods is
> part of the design goal for 3.x range.  Your proposal would breaks all of
> these except __iter__ (and make that slower too) in the sense that the
> replacements would require the O(n) calculation of list(self), whereas
> avoiding this is part of the purpose of range.
>
> While some of these methods are rarely used, __reversed__ is certainly not
> rare.  People depend on the fact that the often easy to write
> reversed(up-range(...)) is equivalent in output *and speed* to the often
> harder to write iter(down-range(...).
>
> A trivial case is counting down from n to 0
>   for i in reversed(range(n+1):
> versus
>   for i in range(n, -1, -1):
> People do not always get the latter correct.
>
> Now onsider a more general case, such as
>   r = range(11, 44000000000, 1335)
>   r1 = reversed(r)
> versus the equivalent
>   r2 = iter(range(43999999346, 10, -1335))
>
> 43999999346 is r[-1], which uses __getitem__.
> Using this is much easier than figuring out the following (which
> __reversed__ has built in).
>
> def reversed_start(start, stop, step):
>     rem = (stop - start) % step
>     return stop - (rem if rem else step)
>
> --
> Terry Jan Reedy
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20150702/053c9611/attachment-0001.html>


More information about the Python-ideas mailing list