fluid Module (fluid.py)


#!/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.