1

What is the shortest and best way to execute a piece of code only once no matter how many times the method/functions is called?

The code is inside the method. Just for example:

once = 0
def fun():
  if once == 0 : 
    print 234
    once += 1

Of course this is too much bookkeeping...and not very extensible.

I would like it to be more like with.

 def fun():
   ......
   once : ... code ..
   ......

function/method code around has to be executed on every call...only once is executed the first time only.

I'm using 2.7.

once implementation could be involved, but the usage has to be simple w/o more logic and bookkeeping.

I need this mostly when I'm debugging where some method is called numerous times but i want to print debugging info once or twice...

'warnings' have not exactly, but similar, functionality somehow...don't know how they do it.

martineau
  • 119,623
  • 25
  • 170
  • 301
sten
  • 7,028
  • 9
  • 41
  • 63
  • 1
    it depends on what your code is doing within that method you're calling. – de_classified Mar 02 '20 at 00:05
  • have you looked into singleton design pattern? or you could just use a variable to keep track whether the method has been called or not and return from inside the methods if it is. – syfluqs Mar 02 '20 at 00:08
  • There's no point to reinventing this wheel. The technique has a name -- memoization -- and both tools and common practices that refer to it. The case where you don't have any arguments is the easy case; where it gets more interesting is when you want to remember a possible result for each argument value. – Charles Duffy Mar 02 '20 at 00:08
  • That said, the code that was recently edited into the question makes it *less* clear, not more -- at least until you add commentary (like "this value should never exceed 1"). – Charles Duffy Mar 02 '20 at 00:09
  • it is not memoization – sten Mar 02 '20 at 00:10
  • 1
    Then edit the question to explain. Is all you want to make `once` a global? (Or make your "function" a callable, and the flag an instance method?) – Charles Duffy Mar 02 '20 at 00:10
  • Edited with a duplicate that tells you (correctly!) that you need to put the line `global once` inside your function for it to change the module-scoped version of `once`, rather than create a local by the same name. – Charles Duffy Mar 02 '20 at 00:13
  • "Too much bookkeeping"? No, it's as much as I'd expect, except that you should really restructure your function to start with a line like `if once > 0: return`, so you don't need to indent any *other* lines in its body. As a matter of good practice, the happy path of a function should be on the left wherever possible. – Charles Duffy Mar 02 '20 at 00:14
  • Rather important: your `print` syntax suggests you are using an obsolete version of Python (<3). Is that correct? It means you cannot use a decorator, for instance. – Jongware Mar 02 '20 at 00:15
  • Anyhow, if you *do* consider that too much bookkeeping, you can always write a decorator to centralize it so it's done only once. Or, of course, you can use a preexisting memoization decorator, which will do exactly what you need out-of-the-box without changes for your current definition of `fun`, even if you completely remove all references to `once`. – Charles Duffy Mar 02 '20 at 00:16
  • 1
    @usr2564301, decorators do exist in Python 2. – Charles Duffy Mar 02 '20 at 00:16
  • (I still don't see how what you're asking for is *different from* memoization in any way whatsoever: Memoization caches a result and doesn't run the body after a function has been called once. Your goal is to not run the body after a function has been called once. What pertinent difference exists that makes a memoization centric answer not helpful to you?) – Charles Duffy Mar 02 '20 at 00:18
  • 1
    Ahh. *Now* you have a concrete request (a context manager that does the job). That's a new and distinct question; reopened. – Charles Duffy Mar 02 '20 at 00:20
  • (@CharlesDuffy: hmm :) Apart from that, I still feel the exact Python version in use may be important.) – Jongware Mar 02 '20 at 00:20
  • 1
    That said, I'm not sure this is going to be possible. If you read the [motivation section of PEP-343](https://www.python.org/dev/peps/pep-0343/#motivation-and-summary), it's very clear that its authors considered allowing control over execution flow (and thus functionality equivalent to LISP-style macros) was something they intentionally sought to avoid. – Charles Duffy Mar 02 '20 at 00:25
  • @sten, an option could be to do whatever 'one time' initialization needs to be done in another function, or block of code, then you don't have to check for it every time you invoke your function. Another option is you could assign another version of the function to whatever name is in its scope. Kind of hackish. – Todd Mar 02 '20 at 00:34
  • @sten, re: warnings, you wouldn't like how they're implemented -- it's an explicit check with a lot of code/logic, nothing short and magical at all. See `filters` in https://github.com/python/cpython/blob/3.8/Lib/warnings.py, maintaining an explicit list of which warnings have already been thrown from which locations. – Charles Duffy Mar 02 '20 at 00:39
  • This is a job for `if`, not `with`. Depending on how this thing should behave in the face of concurrent calls, I might use a lock and `if lock.acquire(blocking=False): ...`. – user2357112 Mar 02 '20 at 00:45
  • Not clear why you need python2.7 considering it's EOL – OneCricketeer Mar 02 '20 at 02:56
  • In case you're still hoping for an answer with a `with` statement: Following answers ( https://stackoverflow.com/questions/12594148/skipping-execution-of-with-block ) seem to indicate, that this is rather unlikely except you're ready for heavy hacking / magic / code using features that are not part of python's specification / etc. Code would probably not even be more efficient when below given answers – gelonida Mar 06 '20 at 00:36
  • @sten None of the given answers is satisfactory. for you? Please read also all comments. – gelonida Mar 11 '20 at 00:48

5 Answers5

1

So I'm not sure if the motivation for the question was simply wanting an elegant approach that's syntactically pleasing... or if there are any concerns about performance of having one-time code within a function that is skipped on every call to it. I'll just assume it's a performance concern (which is kind of moot - see note at bottom).

If one-time behavior can be done somewhere else in the code and taken out of the function, that's an option to consider. But if it cant...

One way to optimize function foo() for instance that does some one-time behavior, is to replace it with another version of itself after it's done it's one-time work. That eliminates any extra instructions on subsequent calls. foo() can just reassign the other version to the name referencing it within its scope.

>>> def foo():
...     global foo
...     print("doing some lengthy initialization only needed once.")
...     print("onto other things...")
...     foo = _foo
...     
>>> def _foo():
...     print("onto other things...")
...   

On the other hand, if you were to put the one-time behavior into another function that foo() calls, then that function itself can overwrite itself in the same way. But foo() retains some overhead since it always still tries to call it on each invokation.

Redefining the one-time function is a strategy that could be done inside the one-time function itself like so:

>>> def slow_initialization():
...     global slow_initialization
...     print("taking forever to update database - once...")
...     slow_initialization = _disable_slow_initialization
...     
>>> def _disable_slow_initialization():
...     pass
...     
>>> def foo():
...     slow_initialization()
...     print("now doing other stuff...")
...     
>>> foo()
taking forever to update database - once...
now doing other stuff...
>>> 
>>> foo()
now doing other stuff...
>>> 

The first example is obviously optimal instruction-wise.

Considering other approaches, there's not going to be much performance difference between having an init-like function that checks a variable then returns, over one that replaces itself.

You can see below that the second most efficient way to deal with one-time behavior is just to code it within the function that needs it and check a variable to see if it's already been called (last example below)

>>> # foo() calling a one-time init function that checks a var then 
>>> # returns. The init function incurs 4 instructions after the one 
>>> # time behavior has been done (not including the jump to it from foo().
>>> def init_strategy_1():
...     if _initialized:
...         return
...     print("taking forever to update database - once...")
>>>
>>> dis.dis(init_strategy_1)
  2           0 LOAD_GLOBAL              0 (_initialized)
              2 POP_JUMP_IF_FALSE        8

  3           4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
      [[[------ truncated -------]]]
>>>
>>> # If the one-time function were replaced by a no-op function, the
>>> # cost is just two instructions to jump back to foo()
>>> def init_strategy_2():
...     pass
>>>
>>> dis.dis(init_strategy_2)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
>>> 
>>>
>>> # Placing the one-time code in another function incurs a few 
>>> # instructions to call the function from within foo().
>>> def foo():
...     init_strategy()
...     print("doing other things...")
... 
>>> dis.dis(foo)

  2           0 LOAD_GLOBAL              0 (init_strategy)
              2 CALL_FUNCTION            0
              4 POP_TOP
>>>
>>>
>>> # Instructionwise, the most efficient way to implement one-time
>>> # behavior is to check a variable within foo() and skip the block.
>>> def foo():
...     if not _initialized:
...         print("performing initialization tasks...")
...     print("Now doing other things...")
...
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (_initialized)
              2 POP_JUMP_IF_TRUE        12
       [[[------ truncated -------]]]
...

summarizing...

  • One time behavior within another function.

    • One time function checks a var to see if it's done then returns.

      • 7 instructions wasted per call to foo() after one-time done.
    • One time function replaced by a no-op after task done.

      • 5 instructions wasted per call to foo() after one-time done.
  • foo() itself checks a variable and then skips the one-time block.

    • 2 instructions to check a var then jump if task was already done.

Trying to optimize code in the ways described above are probably not worthwhile if expecting any significant performance gain. Scripting code is already slow, and 2-7 instructions to bypass one-time behavior isn't significant. If a certain function has been identified as slowing everything down, then either the algorithms it hosts needs to be reworked, or it could be replaced by native code, or both.

Todd
  • 4,669
  • 1
  • 22
  • 30
  • Sorry if my answer is off-topic, but I suspected it wasn't. I also wanted to explore this for personal reasons because I've often wondered about this while coding. C has the `static` keyword that can be put at the top of a function and any function calls within its scope are only called once, and the function wastes no instructions after that on it. I don't think there's an analog in Python for that. – Todd Mar 02 '20 at 03:02
  • 1
    Even if this answer isn't useful to the OP (and my impression is very much that their goal is to have something syntactically pleasing), it strikes me as a good knowledge base entry overall. We're not just writing for the OP, we're writing for everyone with the same question. – Charles Duffy Mar 02 '20 at 15:54
  • Thanks @CharlesDuffy, i appreciate the feedback =) – Todd Mar 02 '20 at 18:02
1

Have you considered a decorator? Something like:

import functools

class once:
    """
    Function decorator to allow only one single execution of 
    a function; however, always return the result of that one
    and only execution.
    """
    def __init__(self, wrapped):
        self.wrapped = wrapped
        functools.update_wrapper(self, wrapped)

    def __call__(self, *args, **kwargs):
        if not hasattr(self, "retval"):
            self.retval = self.wrapped(*args, **kwargs)
        return self.retval

You can then decorate your function(s) as follows:

Python 2.7.17 (default, Oct 20 2019, 14:46:50) 
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> @once
... def fun():
...     print 234
... 
>>> fun()
234
>>> fun()
>>> 

I’d look at this as a better way to manage the global state mentioned above.

Jens
  • 8,423
  • 9
  • 58
  • 78
1

You could do something like that with a function decorator that injected a variable named once into the decorated function's scope.

import functools

def add_once(f):
    d = {'once': 1}
    sentinel = object()

    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        g = f.__globals__

        oldvalue = g.get('once', sentinel)
        g['once'] = d['once']

        try:
            res = f(*args, **kwargs)
        finally:
            if oldvalue is sentinel:
                del g['once']
            else:
                g['once'] = oldvalue

        d['once'] = 0
        return res
    return wrapped

Example usage:

@add_once
def func():
    print('Starting')
    if once:
        print('EXECUTED ONE TIME ONLY')
    print('Ending')

func()
func()

Output:

Starting
EXECUTED ONE TIME ONLY
Ending
Starting
Ending
martineau
  • 119,623
  • 25
  • 170
  • 301
  • cool.. can u explain how it works..... how do I add additional arg to the decorator, so I can use different vars, not just 'once' ... i.e. i want it to be a parameter instead of hardcode ... – sten Mar 02 '20 at 16:14
  • forget parametrized ... is there a way to reset 'once' if i need it.. i.e to make it ONCE per session ... instead ONCE per forever – sten Mar 02 '20 at 17:11
  • It works by injecting a variable named 'once' into the decorated function's global namespace. `__globals__` is a dictionary holding its global variables. It would be feasible to extend the idea to inject more than one with different names by passing them as arguments an outer decorator "factory" function. – martineau Mar 02 '20 at 17:37
  • Sorry I don't know of a way, offhand, of resetting the variable. – martineau Mar 02 '20 at 17:42
1

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)

gelonida
  • 5,327
  • 2
  • 23
  • 41
0

Similar to gelonida, but using a dict so that you can customize the number of times the sentinal function is called before it returns a false value.

import traceback
import threading

_once_upon_lock = threading.Lock()
_once_upon_dict = {}

def once_upon_a_time(num_tries=1):
    """Use knowledge of where this function is called in source
    code to count how many times it has been called. When 
    num_tries is exceeded, returns 0 so a simple `if` can be
    used to control program flow.
    """
    # lock protects multithreading
    parent = traceback.extract_stack(limit=2)[0]
    with _once_upon_lock:
        # get filename + line number of the caller. This makes
        # every usage of this function in source code unique.
        key = "{}:{}".format(parent[0], parent[1])
        # see how many times this call has been made, defaulting 
        # to zero
        new_val = _once_upon_dict.setdefault(key, 0) + 1
        # times up? return a falsy value, else the number 
        # of times we've made this call.
        if new_val > num_tries:
            return 0
        else:
            _once_upon_dict[key] = new_val
            return new_val


def bar():
    print "bar", once_upon_a_time() # note: calls on different lines
    print "bar", once_upon_a_time() #       are independent

def foo():
    bar()

def main():
    for i in range(3):
        foo()
    for i in range(3):
        if once_upon_a_time(2):
            print("main")
    print _once_upon_dict.keys()

main()
tdelaney
  • 73,364
  • 6
  • 83
  • 116