I don't think you will be able to find any 'decent' implementation with a with
statement (a context manager).
Just check following question: Skipping execution of -with- block
Context managers execute code before entering the code block and after leaving it, but they always execute the code block, except some very heavy hacking is done.
However you might get something similar to what you're looking for with an if
statement and introspection or a key argument . Please note that introspection is very, very, slow so don't use this in performance critical code. For debugging it might be OK if the loops aren't executed too often.
You implement a magic function, that can determine, where in the source code it has been called and does book keeping such, that it returns True for the first time for each caller's location and False otherwise.
In my opinion the name first_time()
might be clearer than once()
but that's just a detail.
The usage would be something like:
def f(a):
if first_time():
print("first time f was called (a=%s)" % a)
return 2 * a
def g(a):
if first_time():
print("first time g was called (a=%s)" % a)
return 3 * a
for v in (1, 2):
rslt = f(v)
print("F(%d) = %d" % (v, rslt))
g(3)
g(4)
For python2 that would be:
import inspect
first_state = set()
def first_time():
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calfi = calframe[1]
key = calfi[1], calfi[2]
# print("K", key) # uncomment for debugging
if key in first_state:
return False
first_state.add(key)
return True
For python3 it would be:
import inspect
first_state = set()
def first_time():
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calfi = calframe[1]
key = calfi.filename, calfi.lineno
3 print("K", key) # uncomment for debugging
if key in first_state:
return False
first_state.add(key)
return True
Comparing with martineau's solution:
- My suggested solution will be much slower. It depends on the context whether this is a problem, but look at my alternative solution further down.
- you could have multiple check's per function even in conditional code, they would be evaluated individually
- you do not have to decorate a function to use first_time()
- first_time() can be used at module level, so basically anywhere in your code.
- you'll be able to reset by doing first_state.clear()
- by adding the thread id to the key tuple you could allow to call once per thread
Faster and uglier alternative
For a tight loop below solution is about 20.000 (twenty thousand) times faster
I just measured for a small example with python 3.6
import inspect
first_state = set()
def first_time(*key):
if not key:
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calfi = calframe[1]
key = calfi[1], calfi[2]
# print("K", key) # uncomment for debugging
if key in first_state:
return False
first_state.add(key)
return True
This alternative implementation allows to explicitly pass a unique key for performance critical code. So these explicit params avoid the use of introspection, but make the calling code uglier. For code that it is not performance critical you don't pass a key and it behaves as my originally suggested solution.
Example:
import time
t0 = time.time()
c = 0
for v in range(1000):
if first_time(__file__, "critical_1"):
print("first time within performance critical loop")
c += v
t - time.time() - t0
print("Required time: %f" % t)
Implementation of print_once:
If it is just for printing debug messages, then perhaps implement directly print_once()
import inspect
print_once_state = set()
def print_once(msg, *args, key=None):
if not key:
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
calfi = calframe[1]
key = calfi[1], calfi[2]
if key not in print_once_state:
if msg is None:
print("entered %s first time" % (key,))
else:
print(msg, *args)
print_once_state.add(key)
and an example:
import time
t0 = time.time()
c = 0
for v in range(1000):
print_once("in loop: c = ", c, key=(__file__, "critical_1"))
c += v
t - time.time() - t0
print("Required time: %f" % t)