Programing

함수를 루프에서 한 번만 실행하는 효율적인 방법

lottogame 2020. 11. 27. 07:36
반응형

함수를 루프에서 한 번만 실행하는 효율적인 방법


지금은 다음과 같은 일을하고 있는데 지루해집니다.

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.

참고URL : https://stackoverflow.com/questions/4103773/efficient-way-of-having-a-function-only-execute-once-in-a-loop

반응형