Эффективный способ использования функции выполняется только один раз в цикле
На данный момент я делаю такие вещи, как следующее, что становится утомительным:
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
Я не уверен, что понял вашу проблему, но думаю, вы можете разделить цикл. Со стороны функции и части без нее и сохраните две петли.