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
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.
ReplyDeleteWhen 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
ReplyDeleteFor more fun, and in order to improve the debugging skills of your co-workers, replace
ReplyDelete(lambda:None)
with
random.choice([ob for ob in globals().values() if inspect.isfunction(ob)])
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).
ReplyDeleteStill, 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 :)
Hi Anonymous :)
ReplyDeleteYes, functools.wraps should definitely be used (thanks for the reminder).
And Anonymous 2: I agree, that'd be much more fun :)
it's actually not "call once", it's "call many, execute once"
ReplyDeleteIsn't this also called a "Final"?
ReplyDeletelooks like in python 3 this would be:
ReplyDeletefunc.__code__ = (lambda:None).__code__
Is that right, or is there a prettier way in 3.x?
Anonymous: I'm not sure if this is called a 'final' (final for me is when you can't reassign something).
ReplyDeletearrg: Yes, this code is python 2.x. For python 3 you'd have to set __code__.
Regarding the decorator example, why bother with try/finally? I'd assign the _called just before executing the function itself. What do you think?
ReplyDeleteHi Ron,
ReplyDeleteI guess that'd be Ok too :)