Friday, December 07, 2012

Python tricks: making sure a function is only called once

Sometimes I want a function to be called only a single time (there are many use-cases, but specifically this time I wanted to change the way the Python garbage collection worked when dealing with Qt and threads).

Anyways, usually I did that setting some flag so that the second time a function is called, it'd check that flag and would skip the function in the second time.


After tinkering a bit about it, I came with a shorter version changing the function code which I thought is pretty nice:

 def func():  
   print 'Calling func only this time'  
   func.func_code = (lambda:None).func_code  


Now, calling it a second time will actually execute the lamda:None code.

Yes, I know you'll say it's cryptic, so, I'm including a decorator version below which does the same checking with a variable:

 @call_only_once  
 def func():  
   print 'Calling func only this time'  
 def call_only_once(func):  
   def new_func(*args, **kwargs):  
     if not new_func._called:  
       try:  
         return func(*args, **kwargs)  
       finally:  
         new_func._called = True  
   new_func._called = False  
   return new_func  

11 comments:

Adriano Meis said...

Actually, I would use the version that sets a func.flag, but implemented as a decorator. Once you got rid of the repeated boilerplate, you don't need to make things harder for the readers that don't know about func_code.

Anonymous said...

When doing decorators you should also always use functools.wraps which ensures the docstring is copied and similar issues http://docs.python.org/2/library/functools.html#functools.wraps

Anonymous said...

For more fun, and in order to improve the debugging skills of your co-workers, replace

(lambda:None)

with

random.choice([ob for ob in globals().values() if inspect.isfunction(ob)])

Fabio Zadrozny said...

Hi Adriano, I agree with you (so, in my real use cases I used the version that sets a flag and not the one with the func_code).

Still, I posted it because I believe knowing about func_code has its own uses -- such as providing a different version after a function is called the first time -- maybe to lazy evaluate requisites or some other not so common use-cases :)

Fabio Zadrozny said...

Hi Anonymous :)

Yes, functools.wraps should definitely be used (thanks for the reminder).

And Anonymous 2: I agree, that'd be much more fun :)

Anonymous said...

it's actually not "call once", it's "call many, execute once"

Anonymous said...

Isn't this also called a "Final"?

arrg said...

looks like in python 3 this would be:
func.__code__ = (lambda:None).__code__

Is that right, or is there a prettier way in 3.x?

Fabio Zadrozny said...

Anonymous: I'm not sure if this is called a 'final' (final for me is when you can't reassign something).

arrg: Yes, this code is python 2.x. For python 3 you'd have to set __code__.

Ron Klein said...

Regarding the decorator example, why bother with try/finally? I'd assign the _called just before executing the function itself. What do you think?

Fabio Zadrozny said...

Hi Ron,

I guess that'd be Ok too :)