#!/usr/bin/env python2.2
#
# Author: Eric Tiedemann
# Copyright (c) 2002, Google Inc.
"""Provides the fluid class and the Unbound exception raised on
attempts to access unbound fluids.
The fluid class encapsulates dynamic binding in the Scheme tradition.
"""
import sys as _sys # for _getframe()
# Used to represent the `value' of an unbound fluid.
_undefined_value = ('undefined',)
# Used to find the fluid dictionary in the locals dict of a frame.
# This will have to change to a wacky string if and when Python locals
# dicts change to maps from strings.
_fluid_key = ('fluid key',)
class Unbound(Exception):
"""Thrown on attempts to get() the value of an unbound fluid."""
class fluid:
"""A fluid binding cookie. It can be initialized with a `global' binding.
"""
def __init__(self, value=_undefined_value):
self.value = value
def get(self):
"""get()
Gets my dynamically most recent binding's value.
Throws Unbound if there is no such binding.
"""
# Get our caller's frame.
f = _sys._getframe(1)
# Look for a binding and get it.
while f:
if f.f_locals:
d = f.f_locals.get(_fluid_key)
if d and d.has_key(self):
return d[self]
f = f.f_back
# Otherwise, return the default binding's value if any.
if self.value == _undefined_value:
raise Unbound, self
else:
return self.value
def set(self, v):
"""set(value)
Sets my dynamically most recent binding's value to VALUE.
Throws Unbound if there is no such binding.
"""
# Get our caller's frame.
f = _sys._getframe(1)
# Look for a binding and set it.
while f:
if f.f_locals:
d = f.f_locals.get(_fluid_key)
if d and d.has_key(self):
d[self] = v
return
f = f.f_back
# Otherwise, set the default binding's value if any.
if self.value == _undefined_value:
raise Unbound, self
else:
self.value = v
def bind(self, v):
"""bind(value)
Establish a new binding for me with initial value VALUE. This
binding is visible in the current frame (i.e., function/method)
and callees of it unless/until there's another binding.
"""
# Get our caller's frame.
f = _sys._getframe(1)
# Make sure our caller has locals.
if not f.f_locals:
# Is this doable??
f.f_locals = {}
# Make sure our caller has a fluid dict.
d = f.f_locals.get(_fluid_key)
if not d:
d = {}
f.f_locals[_fluid_key] = d
# Do the deed.
d[self] = v
def __repr__(self):
if self.value == _undefined_value:
return 'fluid()'
else:
return 'fluid(%s)' % self.value
def __str__(self):
if self.value == _undefined_value:
return '<fluid --unbound-->'
else:
return '<fluid %s>' % self.value
if __name__ == '__main__':
# UNIT TEST
pass
def main():
import sys
v = fluid()
def foo(n, f):
v.bind(n + n)
if n != 0:
bar(n - 1, f)
def bar(n, f):
print >>f, v.get()
foo(n, f)
print >>f, v.get()
# This should produce a nice run of descending then ascending even numbers.
foo(5, sys.stdout)
import StringIO
s = StringIO.StringIO()
foo(5, s)
s.flush()
s.seek(0)
if s.read() != '10\n8\n6\n4\n2\n2\n4\n6\n8\n10\n':
print 'recursion test failed'
print 'FAIL'
sys.exit(1)
try:
v.get()
except Unbound, e:
print "unbound exception works as expected for %s" % v
else:
print "unbound exception doesn't work as expected for %s" % v
print 'FAIL'
sys.exit(1)
print 'Pass'
# Copyright (c) 2002,
Peter Norvig
# See also
AIMA:
Python Code;
Python.org:
Tutorial,
Language Ref,
Libraries.