using "private" parameters as static storage?

J. Cliff Dyer jcd at sdf.lonestar.org
Thu Nov 13 19:15:04 CET 2008


On Thu, 2008-11-13 at 11:19 -0600, Chris Mellon wrote:
> On Thu, Nov 13, 2008 at 11:16 AM, Joe Strout <joe at strout.net> wrote:
> > One thing I miss as I move from REALbasic to Python is the ability to have
> > static storage within a method -- i.e. storage that is persistent between
> > calls, but not visible outside the method.  I frequently use this for such
> > things as caching, or for keeping track of how many objects a factory
> > function has created, and so on.
> >
> > Today it occurred to me to use a mutable object as the default value of a
> > parameter.  A simple example:
> >
> > def spam(_count=[0]):
> >     _count[0] += 1
> >     return "spam " * _count[0]
> >
> >>>> spam()
> > 'spam '
> >>>> spam()
> > 'spam spam '
> >
> > This appears to work fine, but it feels a little unclean, having stuff in
> > the method signature that is only meant for internal use.  Naming the
> > parameter with an underscore "_count" makes me feel a little better about
> > it.  But then, adding something to the module namespace just for use by one
> > function seems unclean too.
> >
> > What are your opinions on this idiom?  Is there another solution people
> > generally prefer?
> >
> > Ooh, for a change I had another thought BEFORE hitting Send rather than
> > after.  Here's another trick:
> >
> > def spam2():
> >     if not hasattr(spam2,'count'):spam2.count=0
> >     spam2.count += 1
> >     return "spam2 " * spam2.count
> >
> > This doesn't expose any uncleanliness outside the function at all.  The
> > drawback is that the name of the function has to appear several times within
> > itself, so if I rename the function, I have to remember to change those
> > references too.  But then, if I renamed a function, I'd have to change all
> > the callers anyway.  So maybe this is better.  What do y'all think?
> >
> 
> Static storage is a way of preserving state. Objects are a way of
> encapsulating state and behavior. Use an object.

He is using an object.  Specifically, he's using a function object.
Though perhaps you meant put it into a class.

Here are a few essays into the matter

>>> def foo():
...     foo._count += 1
...     return ("spam " * foo.count).rstrip()
... 
>>> foo._count=0
>>> foo()
'spam'
>>> foo()
'spam spam'
>>>

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.

Attempt #2 -- put it in a decorator

>>> def add_counter(f):
...     f._count = 0
...     return f
... 
>>> @add_counter
... def foo():
...     foo._count += 1
...     return ("spam " * foo._count).rstrip()
>>> foo()
'spam'
>>> foo()
'spam spam'
>>>

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.  This is the first decorator I've written that doesn't
define a function inside itself.

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).rstrip()
... 
>>> foo = Foo()
>>> foo()
'spam'
>>> foo()
'spam spam'
>>>

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).  But the "object" semantics are essentially the same, and
state is just as legitimately preserved on a function object as a Foo
object.  

Cheers,
Cliff





More information about the Python-list mailing list