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:

  1. 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.

    ReplyDelete
  2. Anonymous8:30 PM

    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

    ReplyDelete
  3. Anonymous11:59 PM

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

    ReplyDelete
  4. 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 :)

    ReplyDelete
  5. Hi Anonymous :)

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

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

    ReplyDelete
  6. Anonymous2:37 PM

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

    ReplyDelete
  7. Anonymous8:43 AM

    Isn't this also called a "Final"?

    ReplyDelete
  8. 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?

    ReplyDelete
  9. 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__.

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

    ReplyDelete
  11. Hi Ron,

    I guess that'd be Ok too :)

    ReplyDelete