使函数的有效方式仅在循环中执行一次

时间:2010-11-05 05:25:09

标签: python

目前,我正在做类似下面的事情,这很乏味:

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

我猜是有一些更可接受的方法来处理这些东西?

我正在寻找的是按需执行一次函数。例如,按下某个按钮。它是一个交互式应用程序,具有许多用户控制的开关。每个交换机都有一个垃圾变量,只是为了跟踪它是否已经运行,似乎效率低下。

20 个答案:

答案 0 :(得分:98)

我会在函数上使用装饰器来处理它运行的次数。

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

答案 1 :(得分:15)

另一种选择是将函数的func_code code object设置为不执行任何操作的函数的代码对象。这应该在你的函数体的末尾完成。

例如:

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()的所有后续调用都不会做任何事。

这种技术不如accepted answer中提出的装饰器方法灵活,但如果你只想运行一次函数,可能会更简洁。

答案 2 :(得分:6)

在循环之前运行该函数。例如:

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

这是显而易见的解决方案。如果不仅仅满足于眼睛,解决方案可能会更复杂一些。

答案 3 :(得分:5)

我假设这是一个你想要最多执行一次的动作,如果满足某些条件的话。由于您不会总是执行操作,因此无法在循环外无条件执行操作。如果你收到请求,可能会懒惰地检索一些数据(并缓存它),但不会检索它。

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 :(得分:4)

有很多方法可以做你想要的事情;但是,请注意,很可能 - 如问题中所述 - 您不必在循环内调用该函数。

如果你坚持在循环中进行函数调用,你也可以这样做:

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

答案 5 :(得分:4)

我想到了另一种 - 稍微不同寻常但非常有效的方法,不需要装饰器功能或类。相反,它只使用一个mutable关键字参数,它应该适用于大多数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在他的answer中建议并使用迭代器(在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

答案 6 :(得分:2)

这里的答案并不涉及重新分配功能,但仍然可以避免需要那些丑陋的首先"检查。

Python 2.5及更高版本支持

__missing__

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

答案 7 :(得分:1)

假设有一些原因导致在循环

之前无法调用myFunction()
from itertools import count
for i in count():
    if i==0:
        myFunction()

答案 8 :(得分:1)

这是一种明确的编码方式,其中调用函数的状态保持在本地(因此避免了全局状态)。我不太喜欢其他答案中建议的非显式形式:看到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)()

答案 9 :(得分:0)

我使用 functools 的 cached_property 装饰器只运行一次并保存值。来自官方文档的示例 https://docs.python.org/3/library/functools.html

class DataSet:

def __init__(self, sequence_of_numbers):
    self._data = tuple(sequence_of_numbers)

@cached_property
def stdev(self):
    return statistics.stdev(self._data)

答案 10 :(得分:0)

您还可以在函数前面使用标准# Under Apache 2.0 licence COMMIT_RANGE=${COMMIT_RANGE:-$(git merge-base origin/master HEAD)".."} # Go to the root of the repo cd "$(git rev-parse --show-toplevel)" # Get a list of the current files in package form by querying Bazel. files=() for file in $(git diff --name-only ${COMMIT_RANGE} ); do files+=($(bazel query $file)) echo $(bazel query $file) done # Query for the associated buildables buildables=$(bazel query \ --keep_going \ --noshow_progress \ "kind(.*_binary, rdeps(//..., set(${files[*]})))") # Run the tests if there were results if [[ ! -z $buildables ]]; then echo "Building binaries" bazel build $buildables fi tests=$(bazel query \ --keep_going \ --noshow_progress \ "kind(test, rdeps(//..., set(${files[*]}))) except attr('tags', 'manual', //...)") # Run the tests if there were results if [[ ! -z $tests ]]; then echo "Running tests" bazel test $tests fi functools.lru_cache装饰器之一:

从functools导入lru_cache

@lru_cache def cheap_function(): 不返回

https://docs.python.org/3/library/functools.html

答案 11 :(得分:0)

视情况而定,可以使用以下替代装饰器:

from itertools import chain, repeat

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

这个想法是基于迭代器的,迭代器一次生成函数(或使用repeat(muFunction, n) n次),然后无休止地lambda不执行任何操作。

主要优点是您不需要有时会使事情变得复杂的装饰器,这里的一切都发生在一条(我认为是)可读的行中。缺点是您的代码中有一个难看的next

在性能方面似乎没有太大的区别,在我的机器上,这两种方法的开销都在130 ns左右。

答案 12 :(得分:0)

我受s = mapper.set_index('Imported Product')['Product Description'] new_labels = pop.index.levels[0] + '-' + pop.index.levels[0].map(s.get) pop.index.set_levels(new_labels, level=0, inplace=True) print(pop) Imported Product Manufactured Product A-Widget1 x 33871648 y 37253956 B-Widget2 z 18976457 w 19378102 C-Widget3 s 20851820 q 25145561 dtype: int64 函数启发,采用了更灵活的方法:

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

这种方法的令人兴奋的部分是它可以在任何地方使用,并且不需要工厂-它只是一个小的内存跟踪器。

答案 13 :(得分:0)

一个简单的函数,您可以在代码中的许多地方重用(基于此处的其他答案):

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

或等效(如果您想依赖其他库):

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()

样本用法:

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.'

答案 14 :(得分:0)

你拥有所有那些垃圾变量'在主线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 :(得分:0)

如果我正确理解更新的问题,那么这样的事情应该有效

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()

答案 16 :(得分:0)

一种面向对象的方法,使您的函数成为一个类,又称为“仿函数”,其实例会在创建每个实例时自动跟踪它们是否已运行。

由于您更新的问题表明您可能需要其中许多问题,因此我已使用class factory模式更新了我的答案以解决这个问题。这有点不寻常,因为这个原因可能已经被投票了(虽然我们永远不会确定,因为他们从未发表过评论)。它也可以用元类来完成,但它并不简单。

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()方法。

而且,是的,根据您添加到问题中的信息,它会适用。

答案 17 :(得分:0)

为什么这与您的代码有什么不同?

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

答案 18 :(得分:-1)

如果只有在循环中才需要进行条件检查,那么有一个标志表示您已经运行了该功能有帮助。在这种情况下,你使用了一个计数器,一个布尔变量可以正常工作。

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

答案 19 :(得分:-2)

我不确定我是否理解你的问题,但我认为你可以分开循环。在函数和没有它的部分,保存两个循环。