함수를 루프에서 한 번만 실행하는 효율적인 방법
지금은 다음과 같은 일을하고 있는데 지루해집니다.
run_once = 0
while 1:
if run_once == 0:
myFunction()
run_once = 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
또 다른 옵션은 설정하는 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
함수의 실행 코드를 lambda : None에 대한 코드로 대체하므로 이후의 모든 호출 run_once()
은 아무 작업도 수행하지 않습니다.
이 기술은 허용 된 답변 에서 제안 된 데코레이터 접근 방식보다 덜 유연 하지만 한 번 실행하려는 함수가 하나만있는 경우 더 간결 할 수 있습니다.
루프 전에 함수를 실행하십시오. 예:
myFunction()
while True:
# all the other code being executed in your loop
이것은 명백한 해결책입니다. 눈에 보이는 것보다 더 많은 것이 있다면 솔루션이 조금 더 복잡 할 수 있습니다.
일부 조건이 충족되는 경우이 작업을 최대 한 번만 수행하려는 작업이라고 가정합니다. 항상 작업을 수행하는 것은 아니므로 루프 밖에서 무조건 수행 할 수는 없습니다. 요청을 받으면 일부 데이터를 느리게 검색하고 캐싱하는 것과 같지만 그렇지 않으면 검색하지 않습니다.
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()
원하는 작업을 수행하는 방법에는 여러 가지가 있습니다. 그러나 질문에서 설명한대로 루프 내에서 함수를 호출 할 필요가 없을 가능성이 매우 높습니다.
루프 내에서 함수 호출을 고집한다면 다음을 수행 할 수도 있습니다.
needs_to_run= expensive_function
while 1:
…
if needs_to_run: needs_to_run(); needs_to_run= None
…
데코레이터 함수 나 클래스를 필요로하지 않는이 작업을 수행하는 다른 방법 (약간 특이하지만 매우 효과적인 방법)을 생각했습니다. 대신 대부분의 Python 버전에서 작동해야하는 변경 가능한 키워드 인수를 사용합니다. 일반적으로 기본 인수 값이 call-to-call에서 변경되는 것을 원하지 않기 때문에 대부분의 경우 피해야합니다. 그러나이 경우이 기능을 활용하여 저렴한 스토리지 메커니즘으로 사용할 수 있습니다. 작동 방식은 다음과 같습니다.
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
여기에 기능의 재 할당을 포함하지 않는 대답이 있지만 여전히 그 추악한 "첫 번째"확인의 필요성을 방지합니다.
__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
myFunction()
루프 전에 호출 할 수없는 이유가 있다고 가정 합니다.
from itertools import count
for i in count():
if i==0:
myFunction()
다음은이를 코드화하는 명시적인 방법입니다. 여기서 함수가 호출 된 상태는 로컬로 유지됩니다 (따라서 전역 상태는 피합니다). 나는 다른 답변에서 제안 된 비 명시 적 형식을별로 좋아하지 않습니다. f ()를 보는 것은 너무 놀랍고 이것이 f ()가 호출된다는 의미가 아닙니다.
이것은 dict에서 키를 찾고, dict에서 키를 제거하고, 키를 찾을 수없는 경우 사용할 기본값을 사용하는 dict.pop을 사용하여 작동합니다.
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)()
이것이 귀하의 코드와 다른 이유는 무엇입니까?
myFunction()
while 1:
# rest of your code
pass
업데이트 된 질문을 올바르게 이해하면 다음과 같이 작동합니다.
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()
하나의 객체 지향 접근 방식으로 함수를 "펑터"라고하는 클래스로 만들어 각 인스턴스가 생성 될 때 인스턴스가 실행되었는지 여부를 자동으로 추적합니다.
업데이트 된 질문은 많은 것이 필요할 수 있음을 나타내므로 클래스 팩토리 패턴 을 사용하여 문제를 처리하기 위해 답변을 업데이트했습니다 . 이것은 약간 특이한 일이며, 그 이유 때문에 반대 투표를 받았을 수 있습니다 (댓글을 남기지 않았기 때문에 확실히 알 수는 없지만). 메타 클래스로도 가능하지만 그다지 간단하지는 않습니다.
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()
원하는 경우 펑터가 생성되고 메서드가 호출 될 때 일반 및 키워드 인수를 메서드 에 전달할 수도 있습니다 .
그리고 예, 질문에 추가 한 정보를 고려할 때 적용 할 수 있습니다.
You have all those 'junk variables' outside of your mainline while True
loop. To make the code easier to read those variables can be brought inside the loop, right next to where they are used. You can also set up a variable naming convention for these program control switches. So for example:
# # _already_done checkpoint logic
try:
ran_this_user_request_already_done
except:
this_user_request()
ran_this_user_request_already_done = 1
Note that on the first execution of this code the variable ran_this_user_request_already_done
is not defined until after this_user_request()
is called.
A simple function you can reuse in many places in your code (based on the other answers here):
def firstrun(keyword, _keys=[]):
"""Returns True only the first time it's called with each keyword."""
if keyword in _keys:
return False
else:
_keys.append(keyword)
return True
or equivalently (if you like to rely on other libraries):
from collections import defaultdict
from itertools import count
def firstrun(keyword, _keys=defaultdict(count)):
"""Returns True only the first time it's called with each keyword."""
return not _keys[keyword].next()
Sample usage:
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.'
I've taken a more flexible approach inspired by functools.partial
function:
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
With this approach you are able to have more complex and explicit interactions:
do_once('foobar', print, "first try")
do_once('foo', print, "first try")
do_once('bar', print, "second try")
# first try
# second try
The exciting part about this approach it can be used anywhere and does not require factories - it's just a small memory tracker.
Depending on the situation, an alternative to the decorator could be the following:
from itertools import chain, repeat
func_iter = chain((myFunction,), repeat(lambda *args, **kwds: None))
while True:
next(func_iter)()
The idea is based on iterators, which yield the function once (or using repeat(muFunction, n)
n
-times), and then endlessly the lambda doing nothing.
The main advantage is that you don't need a decorator which sometimes complicates things, here everything happens in a single (to my mind) readable line. The disadvantage is that you have an ugly next
in your code.
Performance wise there seems to be not much of a difference, on my machine both approaches have an overhead of around 130 ns.
If the condition check needs to happen only once you are in the loop, having a flag signaling that you have already run the function helps. In this case you used a counter, a boolean variable would work just as fine.
signal = False
count = 0
def callme():
print "I am being called"
while count < 2:
if signal == False :
callme()
signal = True
count +=1
I'm not sure that I understood your problem, but I think you can divide loop. On the part of the function and the part without it and save the two loops.
'Programing' 카테고리의 다른 글
document.body.scrollTop은 스크롤 할 때도 IE에서 항상 0입니다. (0) | 2020.11.27 |
---|---|
모든 mysql 테이블을 별도의 파일에 자동으로 덤프합니까? (0) | 2020.11.27 |
WebViewClient에서 일반 JavaScript 활성화 (0) | 2020.11.27 |
감독자 및 환경 변수 (0) | 2020.11.27 |
정의되지 않은 경우 자동으로 개체 생성 (0) | 2020.11.27 |