Эффективный способ выполнения функции только один раз в цикле - PullRequest
59 голосов
/ 05 ноября 2010

В данный момент я делаю что-то вроде следующего, что становится утомительным:

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

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

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

Ответы [ 17 ]

100 голосов
/ 05 ноября 2010

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

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
15 голосов
/ 22 июля 2014

Другой вариант - установить 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() ничего не сделают.

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

7 голосов
/ 05 ноября 2010

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

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

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

5 голосов
/ 05 ноября 2010

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

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()
4 голосов
/ 07 ноября 2010

Я подумал о другом - немного необычном, но очень эффективном - способе сделать это, который не требует функций или классов декоратора. Вместо этого он просто использует изменяемый ключевой аргумент, который должен работать в большинстве версий 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
4 голосов
/ 05 ноября 2010

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

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

needs_to_run= expensive_function
while 1:
    …
    if needs_to_run: needs_to_run(); needs_to_run= None
    …
2 голосов
/ 29 сентября 2012

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

__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
1 голос
/ 06 ноября 2010

Вот явный способ закодировать это, где состояние, из которого были вызваны функции, сохраняется локально (таким образом, избегается глобальное состояние).Мне не очень нравятся неявные формы, предложенные в других ответах: слишком удивительно видеть 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)()
1 голос
/ 05 ноября 2010

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

from itertools import count
for i in count():
    if i==0:
        myFunction()
0 голосов
/ 07 марта 2018

У вас есть все эти «ненужные переменные» вне вашей основной линии 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().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...