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:

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


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

Anonymous said...

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



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 :)