473,378 Members | 1,407 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,378 software developers and data experts.

delay and force in Python


Here is a question for people who are more comfortable than
I am with new Python stuff like generators.

I am having fun implementing things from the Wizard book
(Abelson, Sussman, "Structure and Interpretation of Computer
Programs") in Python. In chapter 3.5 it is about streams as
delayed lists.

Streams are interesting because they are to lists like
xrange is to range. Could save a lot on memory and
computations.

Below is my straight translation from Scheme code in the
Wizard book to Python. It works, but now I want to rewrite
the delay and force functions using Python's new stuff,
generators or iterators or what-have-you. I have the
feeling that that is possible, can you do it?

The program below creates a stream with the numbers 1..995
and then filters the stream, keeping only the even numbers,
and then prints the second number in the stream (implemented
as the first number of the tail, just like in the 3.5
Section in the Wizard book).
.. # file: teststreams.py
..
.. def delay(exp): return lambda: exp
..
.. def force(o): return o()
..
.. def cons_stream(a,b): return [a, delay(b)]
..
.. def stream_hd(s): return s[0]
..
.. # we use tl for cdr
.. def tl(x):
.. if len(x) == 2: return x[1]
.. else: return x[1:]
..
.. def stream_tl(s): return force(tl(s))
..
.. def stream_enumerate_interval(low, high):
.. if low > high:
.. return None
.. else:
.. return cons_stream(
.. low,
.. stream_enumerate_interval(low+1, high))
..
.. def stream_filter(pred, s):
.. if s is None:
.. return None
.. elif pred(stream_hd(s)):
.. return cons_stream(
.. stream_hd(s),
.. stream_filter(pred, stream_tl(s)))
.. else:
.. return stream_filter(pred, stream_tl(s))
..
.. def isEven(n): return n % 2 == 0
..
.. print stream_hd(stream_tl(stream_filter(
.. isEven,
.. stream_enumerate_interval(1,995))))
.. # 4

Something else: this crashes with a "maximum recursion reached"
.. print stream_enumerate_interval(1,998)

while this does not crash
.. print stream_enumerate_interval(1,900)
this means Python has a maximum of something like 900
recursions?

Jul 18 '05 #1
17 2433
Check this out:

def mygen():
for i in xrange(0, 10):
print "about to yield",i
yield i

g = mygen()
print "calling next()", g.next()
print "calling next()", g.next()
print "calling next()", g.next()

Jul 18 '05 #2
Will Stuyvesant wrote:
Streams are interesting because they are to lists like
xrange is to range. Could save a lot on memory and
computations.
I think you're looking for generators.
Below is my straight translation from Scheme code in the
Wizard book to Python. Something else: this crashes with a "maximum recursion reached"
. print stream_enumerate_interval(1,998)


Unlike Scheme, Python isn't designed for heavily recursive algorithms.
Here's a more Pythonic equivalent of your code:

def count(start, stop):
i = start
while i < stop:
yield i
i += 1

def even(gen):
for x in gen:
if x % 2 == 0:
yield x

numbers = even(count(1, 999))
first = numbers.next()
second = numbers.next()

print second
--
Benji York
be***@benjiyork.com

Jul 18 '05 #3
Will Stuyvesant wrote:
The program below creates a stream with the numbers 1..995
and then filters the stream, keeping only the even numbers,
and then prints the second number in the stream (implemented
as the first number of the tail, just like in the 3.5
Section in the Wizard book).
How's this:

Py> from itertools import islice
Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4

Breaking it into pieces:

Py> from itertools import islice
Py> stream = (x for x in xrange(1, 996) if x % 2 == 0)
Py> second_item = islice(stream, 1, 2).next()
Py> print second_item
4

And most of the stream hasn't been consumed yet:
Py> print stream.next()
6
Py> unconsumed = list(stream)
Py> len(unconsumed)
494

And this version has no problem with recursion limits:
Py> print islice((x for x in xrange(1, sys.maxint) if x % 2 == 0), 1, 2).next()
4

(xrange can't handle Python longs, unfortunately, so we *are* constrained by
sys.maxint. However, since my machine only has half a gig of RAM, the above is
still a damn sight quicker than the equivalent list comprehension would be!)
Something else: this crashes with a "maximum recursion reached"
. print stream_enumerate_interval(1,998)

while this does not crash
. print stream_enumerate_interval(1,900)
this means Python has a maximum of something like 900
recursions?


The CPython implementation is limited by the stack size allocated by the C
runtime library. The exact recursion limit is platform dependent, but something
around 1000 sounds fairly normal.

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #4
Will Stuyvesant wrote:
. def delay(exp): return lambda: exp


If you look at the definition of "delay" in SICP, you'll notice that
it's defined as "syntax sugar", in other words, a macro. Since Python
does not have macros, you'll have to just use "lambda", because by
defining "delay" as a function, you're forcing the expression "exp" to
evaluate immediately. In other words, theres a crucial difference between::

delay(sys.stdout.write('evaluated\n'))

and::

lambda: sys.stdout.write('evaluated\n')

If you type in those two snippets, you'll see what I mean.

Coincidentally, I attempted a similar experiment just a couple of days
ago, and here's what I came up with::

# Stream.py - Stream class inspired by SICP

class Stream(object):
pass

class EndOfStream(Exception):
pass

class Item(Stream):
def __init__(self, current, nextf):
self.current = current
self.nextf = nextf

next = property(lambda self: self.nextf())

def fold(self, f, init):
return f(self.current, self.next.fold(f, init))

def __getitem__(self, n):
if n == 0:
return self.current
else:
return self.next[n - 1]

def __str__(self):
return '<Stream.Item: %s, ...>' % self.current
__repr__ = __str__

class End(Stream):
def fold(self, f, init):
return init

def __getitem__(self, n):
raise EndOfStream()

def _fail(self):
raise EndOfStream()

current = property(_fail)
next = property(_fail)

def __str__(self):
return '<Stream.End>'
__repr__ = __str__

Here's how it works::

Python 2.4 (#1, Dec 4 2004, 20:10:33)
[GCC 3.3.3 (cygwin special)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
import Stream
s = Stream.Item(1, lambda: Stream.Item(2, lambda: Stream.End()))
s <Stream.Item: 1, ...> s.current 1 s.next <Stream.Item: 2, ...> s.next.current 2 s.next.next <Stream.End> s.next.next.next Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "Stream.py", line 37, in _fail
raise EndOfStream()
Stream.EndOfStream def evens(n=0): ... return Stream.Item(n, lambda: evens(n + 2))
... s = evens()
s.current 0 s.next.current 2 s.next.next.current

4

I don't know if this is useful or not, but it was an interesting
exercise nonetheless.

Dave
Jul 18 '05 #5
Will Stuyvesant wrote:
Something else: this crashes with a "maximum recursion reached"
. print stream_enumerate_interval(1,998)

while this does not crash
. print stream_enumerate_interval(1,900)
this means Python has a maximum of something like 900
recursions?


See "sys.getrecursionlimit()" and "sys.setrecursionlimit()". The lack of
tail-call optimization means you probably won't get very far with this
approach (nor with mine). ;)

Dave
Jul 18 '05 #6

Yes you are right, if you just want to carry an expression
around then lambda does it; but delay was not intended as a
top-level function. Perhaps you think that my silly stream
implementation in the original post builds the whole list,
but it does not:
o = stream_enumerate_interval(11,121)
print o [11, <function <lambda> at 0x00BA8670>] f = o[1]
r = f()
print r [12, <function <lambda> at 0x00BA86B0>]

instead it builds a list of lambda's, and that is so
desirable <wink>
Others suggested generators indeed, and I can imagine now a
usage pattern like
s = stream_generator(initValue, next, stop, other_params)
stream_hd(stream_tl(stream_filter(isEven,s)))


where the idea is to pass a generator to a function all the
time. What I don't like though is that I then, as I
understand it, have to code some for-loop inside that
function, I try to avoid for-loops. But a functional form
defined with a for-loop does not seem all that bad. Anyway
I have some reading-up to do, about things some posters use
and I forgot to keep up with since I have been coding Python
1.5.2 style for ages now: properties, itertools, ...printed
some stuff already, thanks!

Jul 18 '05 #7
It took me a while to figure out what the "translated" code was trying
to do. Here's a quick example that I think accomplishes the same
thing:
for i, n in enumerate(x for x in xrange(998) if x % 2 == 0):

.... if i == 1:
.... print n
....
2

Jul 18 '05 #8
On Wed, 19 Jan 2005 23:53:28 +1000, Nick Coghlan <nc******@iinet.net.au> wrote:
[...]
Something else: this crashes with a "maximum recursion reached"
. print stream_enumerate_interval(1,998)

while this does not crash
. print stream_enumerate_interval(1,900)
this means Python has a maximum of something like 900
recursions?


The CPython implementation is limited by the stack size allocated by the C
runtime library. The exact recursion limit is platform dependent, but something
around 1000 sounds fairly normal.

ISTM you forgot something ;-)
(I.e., that 1000 is only a default value (don't know if same on all platforms)).
import sys
help(sys.setrecursionlimit)

Help on built-in function setrecursionlimit in module sys:

setrecursionlimit(...)
setrecursionlimit(n)

Set the maximum depth of the Python interpreter stack to n. This
limit prevents infinite recursion from causing an overflow of the C
stack and crashing Python. The highest possible limit is platform-
dependent.

Regards,
Bengt Richter
Jul 18 '05 #9
Of course I meant to put a break out of the loop after the print
statement. Duh on me.

Jul 18 '05 #10
Just for the record, an implementation without using generators,
somewhat like in Sect. 3.5 of the Wizard book, and without recursion
limit problems.
..
.. def stream_hd(s): # the head of the stream
.. return s[0]
..
.. def stream_tl(s): # the tail of the stream
.. return s[1]()
..
.. ##
.. # The low argument is required: the first element of the stream.
.. # You either use high= or stop_f= to define the end of the
.. # stream. Omit both for an infinite stream. stop_f is a function
.. # that takes low as argument and returns True or False.
.. # The next_f function takes one argument (an element of the stream,
.. # same type as the low argument has)
.. # The stop_f function takes one argument (same type as low)
.. # @param low object
.. # @param high object
.. # @param next_f function
.. # @param stop_f function
.. # @return list, a stream
.. def make_stream(low, high=None, next_f=None, stop_f=None):
.. if next_f is None: next_f = lambda x: x + 1
.. if high is not None: # using high
.. if low > high:
.. return None
.. else:
.. return [low, lambda: make_stream(
.. next_f(low), high=high, next_f=next_f)]
.. elif stop_f is not None: # using stop_f
.. if low > stop_f(low):
.. return None
.. else:
.. return [low, lambda: make_stream(
.. next_f(low), next_f=next_f, stop_f=stop_f)]
.. else: # infinite stream
.. return [low, lambda: make_stream(
.. next_f(low), next_f=next_f)]
..
.. ##
.. # iterative version of filter
.. # @param pred function, (stream-element) -> bool
.. # @param s list, a stream
.. # @return list, a stream
.. def stream_filter(pred, s):
.. if s is None: return []
.. while True:
.. elem = stream_hd(s)
.. if pred(elem):
.. return [elem, lambda: stream_filter(pred, stream_tl(s))]
.. else:
.. s = stream_tl(s)
.. if s is None: return []
..
..
.. if __name__ == '__main__':
..
.. def is100some(x): return x % 100 == 0
.. assert(stream_hd(stream_tl(stream_tl(stream_filter (
.. is100some,
.. make_stream(1, 11111111111111))))) == 300)
..
.. def add_33(x): return x + 33
.. assert(stream_hd(stream_tl(stream_filter(
.. is100some,
.. # stream 1,34,67,100,...
.. make_stream(1,999999999, next_f = add_33)))) == 3400)
..
.. assert(stream_hd(stream_tl(stream_filter(
.. is100some,
.. # infinite stream 1,2,...
.. make_stream(1)))) == 200)
..
.. def mod_20000(x): return x % 20000 == 0
.. # this will need more evaluations than the recursion limit count
.. infinite_filter = stream_filter(mod_20000, make_stream(1))
.. assert( stream_hd(stream_tl(infinite_filter)) == 40000 )
..

Jul 18 '05 #11
Nick Coghlan wrote:
Will Stuyvesant wrote:
The program below creates a stream with the numbers 1..995
and then filters the stream, keeping only the even numbers,
and then prints the second number in the stream (implemented
as the first number of the tail, just like in the 3.5
Section in the Wizard book).

How's this:

Py> from itertools import islice
Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4


Wouldn't it be nice if this could be spelt:

print (x for x in xrange(1, 996) if x % 2 == 0)[2]

Well, I just put a patch on SF to enable exactly that:
http://www.python.org/sf/1108272

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #12
Nick Coghlan wrote:
How's this:

Py> from itertools import islice
Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4


Wouldn't it be nice if this could be spelt:

print (x for x in xrange(1, 996) if x % 2 == 0)[2]


as I've always said, the sooner we can all use the itertools goodies without
even noticing, the better.

</F>

Jul 18 '05 #13
Nick Coghlan wrote:
Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4


Wouldn't it be nice if this could be spelt:

print (x for x in xrange(1, 996) if x % 2 == 0)[2]

Well, I just put a patch on SF to enable exactly that:
http://www.python.org/sf/1108272


I like it. Of course you always have to bear in mind that one giant leap for
a list could be _many_ small steps for an iterator.

Peter

Jul 18 '05 #14
Peter Otten wrote:
Nick Coghlan wrote:

Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4


Wouldn't it be nice if this could be spelt:

print (x for x in xrange(1, 996) if x % 2 == 0)[2]

Well, I just put a patch on SF to enable exactly that:
http://www.python.org/sf/1108272

I like it. Of course you always have to bear in mind that one giant leap for
a list could be _many_ small steps for an iterator.


Indeed. The main cases I am thinking of involve picking off the first few items
of an iterator (either to use them, or to throw them away before using the rest).

And if an app actually *needs* random access, there's a reason lists still exist ;)

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #15
Nick Coghlan <nc******@iinet.net.au> writes:
[...]
(xrange can't handle Python longs, unfortunately, so we *are*
constrained by sys.maxint. However, since my machine only has half a
gig of RAM, the above is still a damn sight quicker than the
equivalent list comprehension would be!)

[...]

Other way 'round: if you had even more RAM, the listcomp would be even
slower for this job!
John
Jul 18 '05 #16
On Tue, 25 Jan 2005 23:53:26 +1000, Nick Coghlan <nc******@iinet.net.au> wrote:
Peter Otten wrote:
Nick Coghlan wrote:

Py> print islice((x for x in xrange(1, 996) if x % 2 == 0), 1, 2).next()
4

Wouldn't it be nice if this could be spelt:

print (x for x in xrange(1, 996) if x % 2 == 0)[2]

Well, I just put a patch on SF to enable exactly that:
http://www.python.org/sf/1108272

I like it. Of course you always have to bear in mind that one giant leap for
a list could be _many_ small steps for an iterator.


Indeed. The main cases I am thinking of involve picking off the first few items
of an iterator (either to use them, or to throw them away before using the rest).

And if an app actually *needs* random access, there's a reason lists still exist ;)

You can bail out of a generator expression with a raise-StopIteration expression spelled iter([]).next() ;-)
def show(x): print x,; return x ... list(show(x) for x in xrange(20) if x<8 or iter([]).next()) 0 1 2 3 4 5 6 7
[0, 1, 2, 3, 4, 5, 6, 7]

Well, since
list(show(x) for x in xrange(20) if x<8) 0 1 2 3 4 5 6 7
[0, 1, 2, 3, 4, 5, 6, 7]

this change due to adding iter([]).next() might be more convincing:
list(show(x) for x in xrange(20) if x<8 or True) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

list(show(x) for x in xrange(20) if x<8 or iter([]).next() or True) 0 1 2 3 4 5 6 7
[0, 1, 2, 3, 4, 5, 6, 7]
Applied to example,
print list(x for i,x in enumerate(x for x in xrange(1, 996) if x % 2 ==0) if i<3 or iter([]).next())[2] 6

or in case you just want one item as result,
print list(x for i,x in enumerate(x for x in xrange(1, 996) if x % 2 ==0) if i==2 or i==3 and iter([]).next())[0]

6

Regards,
Bengt Richter
Jul 18 '05 #17
Bengt Richter wrote:
You can bail out of a generator expression with a raise-StopIteration expression spelled iter([]).next() ;-)
>>> list(show(x) for x in xrange(20) if x<8 or iter([]).next() or True)

0 1 2 3 4 5 6 7
[0, 1, 2, 3, 4, 5, 6, 7]


This is both neat and incredibly arcane at the same time :)

Cheers,
Nick.
--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #18

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

12
by: Conrad | last post by:
Greetings, Q: Is there some way to gracefully suspend a python app for a second or so then resume? I could write the classic basic dumb loop-tenzillion-times delay, but that seems inelegant, and...
0
by: jacob c. | last post by:
When I request a URL using urllib2, it appears that urllib2 always makes the request using HTTP 1.0, and not HTTP 1.1. I'm trying to use the "If-None-Match"/"ETag" HTTP headers to conserve...
20
by: Doug Thews | last post by:
I ran into an interesting re-pain delay after calling the Abort() method on a thread, but it only happens the very first time I call it. Every time afterward, there is no delay. I've got a...
7
by: mfeingold | last post by:
I am working on a system, which among other things includes a server and a ..net control sitting in an html page and connected to the server. I ran into a couple of problems, you guys might have...
6
by: Martin | last post by:
Using javascript, I want to force a browser window to close after about 30 minutes of non-use. Can someone point me to a script that can do this? I assume I could use the setTimeout function and...
2
by: ShawnD | last post by:
I'm having some issues when trying to read input off of a pipe using a python script. I'm trying to process packet data from tcpdump in real-time, so it's a filter that needs to read data while the...
0
by: Jonathan Mark | last post by:
hi all... I wrote a seemingly simple function (below) to use Popen3() and select() to run a program and capture its exit status, stdout output, and stderr output. It worked fine until the box...
2
by: dwhall | last post by:
I have 2 python scripts: examples of a producer and a filter, respectively: #! /usr/bin/env python import sys, time if __name__ == "__main__": while True: sys.stdout.write("hello.\r\n")...
6
by: sheldonlg | last post by:
I came across a problem and googling for an answer didn't help. What I want to do is run an AJAX script that sets a hidden variable on the form. I call the AJAX script from a javascript...
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...
0
by: ryjfgjl | last post by:
In our work, we often need to import Excel data into databases (such as MySQL, SQL Server, Oracle) for data analysis and processing. Usually, we use database tools like Navicat or the Excel import...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.