Re: using "private" parameters as static storage?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Joe Strout

    Re: using "private" parameters as static storage?

    On Nov 13, 2008, at 11:15 AM, J. Cliff Dyer wrote:
    Here are a few essays into the matter
    >
    >>>def foo():
    ... foo._count += 1
    ... return ("spam " * foo.count).rstr ip()
    >
    Simple and straightforward , and _count is still encapsulated in the
    function, but it's kind of ugly, because when the function has been
    defined, it is not functional.
    Right, though the hasattr() check I put in my version of this approach
    takes care of that.
    Attempt #2 -- put it in a decorator
    >
    >>>def add_counter(f):
    ... f._count = 0
    ... return f
    ...
    >>>@add_count er
    ... def foo():
    ... foo._count += 1
    ... return ("spam " * foo._count).rst rip()
    Now THAT is something that hadn't occurred to me! I haven't really
    studied decorators yet (and I'm pretty sure Python didn't have them
    when I was last into it a decade ago). This is an interesting solution.
    Now it's complete as soon as the function is defined, but the
    decorator
    is tightly bound to the function, in its use of _count. I find that
    kind of ugly.
    True. And we have two functions where we really only wanted one --
    that's probably worse than just having a function plus a module-level
    variable. Unless, perhaps, we could make a generic "has_cache"
    decorator that can be reused in any method that needs some static
    storage.
    Try three. Let's put it in a class:
    >
    >>>class Foo(object):
    ... def __init__(self, counter_start=0 ):
    ... self._count = counter_start
    ... def __call__(self):
    ... self._count += 1
    ... return ("spam " * self._count).rs trip()
    At first I thought this suggestion was just to add an instance
    variable to whatever class my method was already in, which violates
    encapsulation at the level I'm after. But now I see this is is
    something more subtle: you've made a callable object. We'd presumably
    combine this with a Singleton pattern to get the desired semantics.
    This is clever, but an awful lot of code for such a simple need.
    Essentially, this has the same behavior as the other two. But it
    looks
    cleaner, and you don't have to worry about coupling separate
    functions,
    or refering to a function by name within itself (because you have self
    to work with).
    True. Pity there isn't a way for a function to get a reference to
    itself except by name. Still, when you rename a method, you're going
    to have to update all the callers anyway -- updating a couple of extra
    references within the method is not that much extra effort.

    All of these approaches may turn out to be more trouble than they're
    worth; a module variable isn't THAT ugly. But it's nice to have some
    options.

    At any rate, you (and rurpy) have taken the time to consider the
    question I was actually asking, instead of arguing with me whether I
    should be asking it. I really appreciate that. Your suggestions are
    helpful and have taught me some new Python tricks. Thanks very much!

    Best,
    - Joe

  • Steven D'Aprano

    #2
    Re: using "private&q uot; parameters as static storage?

    On Thu, 13 Nov 2008 19:52:13 -0700, Joe Strout wrote:
    Pity there isn't a way for a function to get a reference to itself
    except by name. Still, when you rename a method, you're going to have
    to update all the callers anyway -- updating a couple of extra
    references within the method is not that much extra effort.
    I have come across a situation where this was a problem.

    I was writing a bunch of different versions of the classic factorial and
    Fibonacci functions, some iterative, some recursive, some with caches,
    and some without. What I wanted to do was do something like this (for
    factorial):


    def iterative_witho ut_cache(n):
    product = 1
    for i in xrange(1, n+1):
    product *= i
    return product

    iterative_with_ cache = decorate_with_c ache(iterative_ without_cache)

    which works. But it doesn't work for the recursive version:

    def recursive_witho ut_cache(n):
    if n <= 1:
    return 1
    else:
    return n*recursive_wit hout_cache(n-1)

    recursive_with_ cache = decorate_with_c ache(recursive_ without_cache)

    for obvious reasons. Solution: the copy-and-paste anti-pattern. Good
    enough for test code, not for real work.

    Now multiply the above by about a dozen slightly different recursive
    versions of the Fibonacci function, and you will see why it was a problem.


    --
    Steven

    Comment

    • Peter Otten

      #3
      Re: using &quot;private&q uot; parameters as static storage?

      Steven D'Aprano wrote:
      On Thu, 13 Nov 2008 19:52:13 -0700, Joe Strout wrote:
      >
      >Pity there isn't a way for a function to get a reference to itself
      >except by name. Still, when you rename a method, you're going to have
      >to update all the callers anyway -- updating a couple of extra
      >references within the method is not that much extra effort.
      >
      I have come across a situation where this was a problem.
      >
      I was writing a bunch of different versions of the classic factorial and
      Fibonacci functions, some iterative, some recursive, some with caches,
      and some without. What I wanted to do was do something like this (for
      factorial):
      >
      >
      def iterative_witho ut_cache(n):
      product = 1
      for i in xrange(1, n+1):
      product *= i
      return product
      >
      iterative_with_ cache = decorate_with_c ache(iterative_ without_cache)
      >
      which works. But it doesn't work for the recursive version:
      >
      def recursive_witho ut_cache(n):
      if n <= 1:
      return 1
      else:
      return n*recursive_wit hout_cache(n-1)
      >
      recursive_with_ cache = decorate_with_c ache(recursive_ without_cache)
      >
      for obvious reasons. Solution: the copy-and-paste anti-pattern. Good
      enough for test code, not for real work.
      >
      Now multiply the above by about a dozen slightly different recursive
      versions of the Fibonacci function, and you will see why it was a problem.
      You can have Python do the copying for you:

      def make_recursive( deco):
      @deco
      def recursive(n):
      if n <= 1:
      return 1
      return n * recursive(n-1)
      return recursive

      def cached(f, _cache={}):
      def g(*args):
      try:
      result = _cache[args]
      except KeyError:
      result = _cache[args] = f(*args)
      return result
      return g

      r1 = make_recursive( lambda f: f)
      r2 = make_recursive( cached)

      Peter

      Comment

      Working...