Эффективный способ использования функции выполняется только один раз в цикле

На данный момент я делаю такие вещи, как следующее, что становится утомительным:

run_once = 0
while 1:
    if run_once == 0:
        myFunction()
        run_once = 1:

Я предполагаю, что есть еще более приемлемый способ обработки этого материала?

Я ищу, чтобы функция выполнялась один раз, по требованию. Например, при нажатии определенной кнопки. Это интерактивное приложение, в котором есть много управляемых пользователем переключателей. Имея мусорную переменную для каждого коммутатора, просто для того, чтобы отслеживать, была ли она запущена или нет, выглядела неэффективной.

Ответ 1

Я бы использовал декоратор функции, чтобы обрабатывать отслеживание того, сколько раз он запускается.

def run_once(f):
    def wrapper(*args, **kwargs):
        if not wrapper.has_run:
            wrapper.has_run = True
            return f(*args, **kwargs)
    wrapper.has_run = False
    return wrapper


@run_once
def my_function(foo, bar):
    return foo+bar

Теперь my_function будет запускаться только один раз. Другие вызовы будут возвращены None. Просто добавьте предложение else в if, если вы хотите, чтобы он возвращал что-то еще. Из вашего примера ничего не нужно возвращать.

Если вы не контролируете создание функции или функция должна использоваться обычно в других контекстах, вы можете просто применить декоратор вручную.

action = run_once(my_function)
while 1:
    if predicate:
        action()

Это оставит my_function доступным для других целей.

Наконец, если вам нужно только запустить его один раз дважды, то вы можете просто сделать

action = run_once(my_function)
action() # run once the first time

action.has_run = False
action() # run once the second time

Ответ 2

Другой вариант - установить func_code объект кода для вашей функции как объект кода для функции, которая ничего не делает. Это должно быть сделано в конце вашего функционального тела.

Например:

def run_once():  
   # Code for something you only want to execute once
   run_once.func_code = (lambda:None).func_code

Здесь run_once.func_code = (lambda:None).func_code заменяет исполняемый код вашей функции кодом для лямбды: Нет, поэтому все последующие вызовы run_once() ничего не сделают.

Этот метод менее гибок, чем подход декоратора, предложенный в принятом ответе, но может быть более кратким, если у вас есть только одна функция, которую вы хотите запустить один раз.

Ответ 3

Запустите функцию перед циклом. Пример:

myFunction()
while True:
    # all the other code being executed in your loop

Это очевидное решение. Если это больше, чем кажется на первый взгляд, решение может быть немного сложнее.

Ответ 4

Я предполагаю, что это действие, которое вы хотите выполнить не чаще одного раза, если выполняются некоторые условия. Поскольку вы не всегда будете выполнять действие, вы не можете сделать это безоговорочно вне цикла. Что-то вроде ленивого извлечения некоторых данных (и кэширования), если вы получаете запрос, но не извлекаете его в противном случае.

def do_something():
    [x() for x in expensive_operations]
    global action
    action = lambda : None

action = do_something
while True:
    # some sort of complex logic...
    if foo:
        action()

Ответ 5

Есть много способов сделать то, что вы хотите; однако, обратите внимание, что вполне возможно, что, как описано в вопросе, вам не нужно вызывать функцию внутри цикла.

Если вы настаиваете на вызове функции внутри цикла, вы также можете:

needs_to_run= expensive_function
while 1:
    …
    if needs_to_run: needs_to_run(); needs_to_run= None
    …

Ответ 6

Я подумал о другом - немного необычном, но очень эффективном - способе сделать это, который не требует функций или классов декоратора. Вместо этого он просто использует изменяемый аргумент ключевого слова, который должен работать в большинстве версий Python. В большинстве случаев этого следует избегать, так как обычно вы не хотите, чтобы значение аргумента по умолчанию изменялось от вызова к вызову, но в этом случае эту способность можно использовать и использовать в качестве дешевого механизма хранения. Вот как это будет работать:

def my_function1(_has_run=[]):
    if _has_run: return
    print("my_function1 doing stuff")
    _has_run.append(1)

def my_function2(_has_run=[]):
    if _has_run: return
    print("my_function2 doing some other stuff")
    _has_run.append(1)

for i in range(10):
    my_function1()
    my_function2()

print('----')
my_function1(_has_run=[])  # Force it to run.

Выход:

my_function1 doing stuff
my_function2 doing some other stuff
----
my_function1 doing stuff

Это можно немного упростить, выполнив то, что @gnibbler предложил в своем ответе, и используя итератор (который был представлен в Python 2.2):

from itertools import count

def my_function3(_count=count()):
    if next(_count): return
    print("my_function3 doing something")

for i in range(10):
    my_function3()

print('----')
my_function3(_count=count())  # Force it to run.

Выход:

my_function3 doing something
----
my_function3 doing something

Ответ 7

Здесь ответ, который не предполагает переназначения функций, но все же предотвращает необходимость в этой уродливой "первой" проверке.

__missing__ поддерживается Python 2.5 и выше.

def do_once_varname1():
    print 'performing varname1'
    return 'only done once for varname1'
def do_once_varname2():
    print 'performing varname2'
    return 'only done once for varname2'

class cdict(dict):
    def __missing__(self,key):
        val=self['do_once_'+key]()
        self[key]=val
        return val

cache_dict=cdict(do_once_varname1=do_once_varname1,do_once_varname2=do_once_varname2)

if __name__=='__main__':
    print cache_dict['varname1'] # causes 2 prints
    print cache_dict['varname2'] # causes 2 prints
    print cache_dict['varname1'] # just 1 print
    print cache_dict['varname2'] # just 1 print

Вывод:

performing varname1
only done once for varname1
performing varname2
only done once for varname2
only done once for varname1
only done once for varname2

Ответ 8

Предполагая, что существует некоторая причина, по которой myFunction() не может быть вызвана до цикла

from itertools import count
for i in count():
    if i==0:
        myFunction()

Ответ 9

Один объектно-ориентированный подход и делает вашу функцию классом, иначе говоря, "функтором", чьи экземпляры автоматически отслеживают, были ли они запущены или нет при создании каждого экземпляра.

Поскольку ваш обновленный вопрос показывает, что вам может понадобиться много из них, я обновил свой ответ, чтобы справиться с этим, используя шаблон фабрики классов. Это немного необычно, и по этой причине, возможно, за него проголосовали (хотя мы никогда не узнаем наверняка, потому что они никогда не оставляли комментарий). Это также можно сделать с помощью метакласса, но это не намного проще.

def RunOnceFactory():
    class RunOnceBase(object): # abstract base class
        _shared_state = {} # shared state of all instances (borg pattern)
        has_run = False
        def __init__(self, *args, **kwargs):
            self.__dict__ = self._shared_state
            if not self.has_run:
                self.stuff_done_once(*args, **kwargs)
                self.has_run = True
    return RunOnceBase

if __name__ == '__main__':
    class MyFunction1(RunOnceFactory()):
        def stuff_done_once(self, *args, **kwargs):
            print("MyFunction1.stuff_done_once() called")

    class MyFunction2(RunOnceFactory()):
        def stuff_done_once(self, *args, **kwargs):
            print("MyFunction2.stuff_done_once() called")

    for _ in range(10):
        MyFunction1()  # will only call its stuff_done_once() method once
        MyFunction2()  # ditto

Выход:

MyFunction1.stuff_done_once() called
MyFunction2.stuff_done_once() called

Примечание: Вы можете сделать функцию/класс способной делать вещи снова, добавив метод reset() к своему подклассу, который сбрасывает общий атрибут has_run. Также можно передать обычные аргументы и аргументы ключевых слов в метод stuff_done_once(), когда функтор создан и при необходимости вызывается метод.

И, да, это будет применимо, учитывая информацию, которую вы добавили в свой вопрос.

Ответ 10

Здесь явный способ закодировать это, где состояние тех функций, которые были вызваны, хранится локально (поэтому избегается глобальное состояние). Мне не нравятся неявные формы, предложенные в других ответах: слишком удивительно видеть f(), и это не означает, что f() вызывается.

Это работает с помощью dict.pop, который ищет ключ в dict, удаляет ключ из dict и принимает значение по умолчанию для использования, если ключ не найден.

def do_nothing(*args, *kwargs):
    pass

# A list of all the functions you want to run just once.
actions = [
    my_function,
    other_function
]
actions = dict((action, action) for action in actions)

while True:
    if some_condition:
        actions.pop(my_function, do_nothing)()
    if some_other_condition:
        actions.pop(other_function, do_nothing)()

Ответ 11

Почему это отличается от вашего кода?

myFunction()
while 1:
    # rest of your code
    pass

Ответ 12

Если я правильно понял обновленный вопрос, что-то вроде этого должно работать

def function1():
    print "function1 called"

def function2():
    print "function2 called"

def function3():
    print "function3 called"

called_functions = set()
while True:
    n = raw_input("choose a function: 1,2 or 3 ")
    func = {"1": function1,
            "2": function2,
            "3": function3}.get(n)

    if func in called_functions:
        print "That function has already been called"
    else:
        called_functions.add(func)
        func()

Ответ 13

Чтобы решить проблему. вам нужно всего лишь изменить код:

run_once = 0
while run_once < 1:
        print("Hello")
        run_once = 1

Ответ 14

У вас есть все эти "ненужные переменные" вне вашего основного цикла while True. Чтобы облегчить чтение кода, эти переменные можно поместить в цикл, рядом с местом их использования. Вы также можете установить соглашение об именовании переменных для этих переключателей управления программой. Так, например:

#                                  # _already_done checkpoint logic
    try:
        ran_this_user_request_already_done
    except:
        this_user_request()
        ran_this_user_request_already_done = 1

Обратите внимание, что при первом выполнении этого кода переменная ran_this_user_request_already_done не определяется до тех пор, пока не будет вызван this_user_request().

Ответ 15

Простая функция, которую вы можете использовать во многих местах своего кода (основываясь на других ответах здесь):

def firstrun(keyword, _keys=[]):
    """Returns True only the first time it called with each keyword."""
    if keyword in _keys:
        return False
    else:
        _keys.append(keyword)
        return True

или эквивалентно (если вам нравится полагаться на другие библиотеки):

from collections import defaultdict
from itertools import count
def firstrun(keyword, _keys=defaultdict(count)):
    """Returns True only the first time it called with each keyword."""
    return not _keys[keyword].next()

Пример использования:

for i in range(20):
    if firstrun('house'):
        build_house() # runs only once
if firstrun(42): # True
    print 'This will print.'
if firstrun(42): # False
    print 'This will never print.'

Ответ 16

Я выбрал более гибкий подход, вдохновленный функцией functools.partial:

DO_ONCE_MEMORY = []

def do_once(id, func, *args, **kwargs):
    if id not in DO_ONCE_MEMORY:
        DO_ONCE_MEMORY.append(id)
        return func(*args, **kwargs)
    else:
        return None

С этим подходом вы можете иметь более сложные и явные взаимодействия:

do_once('foobar', print, "first try")
do_once('foo', print, "first try")
do_once('bar', print, "second try")
# first try 
# second try 

Захватывающая часть этого подхода - его можно использовать где угодно и не требовать фабрики - это всего лишь небольшой трекер памяти.

Ответ 17

В зависимости от ситуации альтернативой декоратору может быть следующее:

from itertools import chain, repeat

func_iter = chain((myFunction,), repeat(lambda *args, **kwds: None))
while True:
    next(func_iter)()

Идея основана на итераторах, которые выдают функцию один раз (или используют repeat(muFunction, n) n -times), а затем бесконечно ничего не делают лямбда.

Основным преимуществом является то, что вам не нужен декоратор, который иногда усложняет вещи, здесь все происходит в одной (на мой взгляд) читаемой строке. Недостатком является то, что у вас в коде ужасный next.

В отношении производительности, похоже, нет большой разницы, на моей машине оба подхода имеют накладные расходы около 130 нс.

Ответ 18

Если проверка состояния должна произойти только после того, как вы находитесь в цикле, вам будет предоставлен флаг, сигнализирующий, что вы уже запускаете функцию. В этом случае вы использовали счетчик, логическая переменная будет работать так же хорошо.

signal = False
count = 0 
def callme(): 
     print "I am being called"  
while count < 2: 
     if signal == False : 
         callme()
         signal = True
     count +=1

Ответ 19

Я не уверен, что понял вашу проблему, но думаю, вы можете разделить цикл. Со стороны функции и части без нее и сохраните две петли.