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


69

目前,我正在做以下事情,这变得很乏味:

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

我猜还有其他一些可以接受的方式来处理这些东西吗?

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


10
如果必须运行一次,为什么不立即调用函数-> myFunction()呢!请看一下您的代码,更好地解释其意图。
— pyfunc

5
您为什么不能将其留在循环之外?请提供更多背景信息。
— 2010

@pyfunc,让我知道代码编辑是否更清楚地传达了这一问题
— 罗恩(Ron)

好吧,循环可能会遍历某些任务的实验参数,并且除一个参数外,所有功能都利用该参数。但是,我最好将它们置于for循环中,因为它们都是该任务的实现。例如,在检索任务中,我的循环遍历我开发的嵌入方法的配置,但基线关键字匹配方法始终输出相同的结果。这样,代码看起来更简洁,同时节省了计算资源。
— 肖恩

Answers:


117

我会在函数上使用装饰器来处理跟踪运行次数的情况。

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

这似乎是我可能正在寻找的东西,但是我不确定您使用的语法。@ char的作用是什么?
— Marcus Ottosson,2010年

4
@Marcus Ottosson@字符使它成为装饰器。评论太长,无法解释是什么。在这里,有一个链接。无论如何,您可能只想使用我提供的第二种形式,而不是手动应用它。您可能仍然应该知道装饰器是什么。
— aaronasterling

1
值得一提的是,这也适用于类的方法。
— Ehtesh Choudhury 2014年

装饰器的新手..有人可以解释一下run_once装饰器的工作原理吗?如果将my_function传递给它,则将wrapper.has_run设置为false,然后再“返回包装器”,然后返回wrapper函数,并将.has_run设置为true并返回my_function,并运行一次。然后,如果以后再调用my_function,将不会再次返回包装器,因为在“返回包装器”之前总是有此“ wrapper.has_run = False”行吗?我在哪里想念?
— 奥拉拉16-10-14

@olala:您不了解的是,run_once装饰器本身仅由@run_once上方的语句运行一次def my_function,因此wrapper.has_run = False仅在示例代码中发生一次。从那时起,wrapper()通过执行此操作创建的函数就是通过调用my_function()来调用的,因此该标志不会被重置。
— martineau

18

另一种选择是设置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()将无效。

这种技术没有接受的答案中建议的装饰器方法灵活,但是如果您只有一个函数要运行一次,则它可能更简洁。


3
在Python 3上run_once.__code__ = (lambda: None).__code__。
— 拉法

7

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

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

这是显而易见的解决方案。如果不只是眼球,解决方案可能会更复杂。


5
这不会减少是因为OP指出“例如,按下某个按钮。它是一个交互式应用程序,具有许多用户控制的开关。” 因此,OP希望函数可以在主运行循环内部运行一次。
— 2014年

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

1
这是假设您想要(或可以)更改do_something()或myFunction()的逻辑以适合这种特殊情况。也许在不同上下文中的其他地方调用了do_something / myFunction。(当然,直到海报澄清后我们才知道。)
— snapshoe 2010年

3
这太令人费解了。整洁的把戏。我的主要抱怨,我认为这是一个严重的抱怨,它do_something需要知道它将会被混叠action,在我看来这违反了某种关注点分离。我重申一下,巧妙的技巧。
— aaronasterling

3
-1。这很麻烦,并且在较大的代码库中可能会损害可读性。全局变量,即name action,每次都会调用一个函数(与原始问题相反,该函数仅被调用一次)。
— Noufal Ibrahim 2010年

2
action();action = lambda:None在没有额外功能的情况下也达到了类似的结果
— John La Rooy 2010年

@gnibbler是的,但是我正在考虑一个更复杂的函数,action可以在各种情况下调用它。也就是说,我喜欢装饰器变体。
— 瑞安·金斯特罗姆

5

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

如果您坚持要在循环内进行函数调用,则还可以执行以下操作:

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

+1我很想删除我的答案,但最近我做的太多了。
— aaronasterling

@aaron:无论您删除/剩余答案的比例如何,您都绝对不应该删除答案:)
— tzot 2010年

1
if needs_to_run is not None此处明确且正确的格式可能是适当的。needs_to_run, run_once = None, needs_to_run; run_once()可以保护免受异常侵扰,减少响应用户击键的多次调用的可能性。
— jfs

4

我想到了另一种不需要装饰器函数或类的方法,该方法有点不寻常,但非常有效。相反,它仅使用可变的关键字参数,该参数应该在大多数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

3

这是一个答案,它不涉及功能的重新分配,但是仍然避免了对丑陋的“首先”检查的需要。

__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

2

假设有某些原因导致myFunction()循环之前无法调用

from itertools import count
for i in count():
    if i==0:
        myFunction()

这个想法很巧妙-听起来OP想要的东西是函数的一个方面,并且对它的任何调用都有效,而不是像这样从外部进行控制。
— martineau 2010年

2

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

由于您更新的问题表明您可能需要其中的许多问题,因此,我已经更新了答案,以使用类工厂模式来解决该问题。这有点不寻常,并且可能由于这个原因被否决了(尽管我们永远无法确定,因为他们从未发表评论)。也可以用一个元类来完成,但这并不简单。

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()如果需要,在创建函子并调用该方法时,也可以将常规参数和关键字参数传递给该方法。

而且,是的,鉴于您添加到问题中的信息,这将适用。


这个看起来也不错。您介意阅读我更新的问题,看看是否可能是一个好的解决方案?
— Marcus Ottosson

@MarcusOttosson:对不起,迟来的答复。如果我了解您更新的问题中的其他信息,那么可以,我认为它可以正常工作。
— martineau

1

这是一种明确的编码方法,其中已调用函数的状态保留在本地(因此避免了全局状态)。我不太喜欢其他答案中建议的非显式形式:看到f()太令人惊讶了,这并不意味着f()被调用。

这是通过使用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)()

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

那将停止交互。看,这个应用程序监视我的平板电脑输入,并通过各种表达式和基本数学对其进行处理,最后将其输出到屏幕上。我认为我所寻找的并不是任何一种独特的方法,因为我要尝试做的就是说,例如,按B键,将2加到任何变量上,以使方程式改变。或者,更改代表屏幕上某些数据的条形之一的颜色。我开始觉得可能不是我目前正在做的事情
— 。.–马库斯·奥托森

因此,需要澄清的是,它不仅需要运行一次,然后再也无法运行。只需在循环内完成操作,然后停止执行即可。例如,将2加到我的方程式中,然后继续做之前的工作,但要使用新用户输入的数据。简单的东西,我想:)
— Marcus Ottosson 2010年

@Marcus,我不建议您使用raw_input()。它就在那里,因此您可以运行该程序并了解其工作原理。在我看来,您的应用程序可以更好地与回调一起使用
— John La Rooy 2010年

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()调用之后才定义变量。


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

0

我受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 

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


0

根据情况,可以使用以下替代装饰器的方法:

from itertools import chain, repeat

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

这个想法是基于迭代器的,迭代器只生成一次函数(或使用repeat(muFunction, n) n-times),然后无休止的lambda无所作为。

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

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



-1

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

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

-2

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


1
这不能回答问题。如果您不了解问题,请在评论中进行澄清。
— 2014年

我第二个杰米。如果您需要更多说明,请发表评论。
— Ajit Vaze
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.