目前,我正在做以下事情,这变得很乏味:
run_once = 0
while 1:
if run_once == 0:
myFunction()
run_once = 1:
我猜还有其他一些可以接受的方式来处理这些东西吗?
我正在寻找的功能是按需执行一次。例如,按下某个按钮。这是一个交互式应用程序,具有许多用户控制的开关。每个开关都有一个垃圾变量,只是为了跟踪它是否已运行,似乎效率很低。
目前,我正在做以下事情,这变得很乏味:
run_once = 0
while 1:
if run_once == 0:
myFunction()
run_once = 1:
我猜还有其他一些可以接受的方式来处理这些东西吗?
我正在寻找的功能是按需执行一次。例如,按下某个按钮。这是一个交互式应用程序,具有许多用户控制的开关。每个开关都有一个垃圾变量,只是为了跟踪它是否已运行,似乎效率很低。
Answers:
我会在函数上使用装饰器来处理跟踪运行次数的情况。
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
@字符使它成为装饰器。评论太长,无法解释是什么。在这里,有一个链接。无论如何,您可能只想使用我提供的第二种形式,而不是手动应用它。您可能仍然应该知道装饰器是什么。
run_once装饰器本身仅由@run_once上方的语句运行一次def my_function,因此wrapper.has_run = False仅在示例代码中发生一次。从那时起,wrapper()通过执行此操作创建的函数就是通过调用my_function()来调用的,因此该标志不会被重置。
另一种选择是设置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()将无效。
这种技术没有接受的答案中建议的装饰器方法灵活,但是如果您只有一个函数要运行一次,则它可能更简洁。
run_once.__code__ = (lambda: None).__code__。
我假设如果满足某些条件,那么您最多只能执行一次此操作。由于您将不会总是执行该操作,因此您无法在循环外无条件地执行该操作。如果您收到请求,就会像懒惰地检索某些数据(并将其缓存)之类的东西,否则就不会检索它。
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()
do_something需要知道它将会被混叠action,在我看来这违反了某种关注点分离。我重申一下,巧妙的技巧。
action,每次都会调用一个函数(与原始问题相反,该函数仅被调用一次)。
action();action = lambda:None在没有额外功能的情况下也达到了类似的结果
action可以在各种情况下调用它。也就是说,我喜欢装饰器变体。
有很多方法可以做您想做的事。但是,请注意,正如问题中所述,很有可能不必在循环内调用该函数。
如果您坚持要在循环内进行函数调用,则还可以执行以下操作:
needs_to_run= expensive_function
while 1:
…
if needs_to_run: needs_to_run(); needs_to_run= None
…
if needs_to_run is not None此处明确且正确的格式可能是适当的。needs_to_run, run_once = None, needs_to_run; run_once()可以保护免受异常侵扰,减少响应用户击键的多次调用的可能性。
我想到了另一种不需要装饰器函数或类的方法,该方法有点不寻常,但非常有效。相反,它仅使用可变的关键字参数,该参数应该在大多数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
这是一个答案,它不涉及功能的重新分配,但是仍然避免了对丑陋的“首先”检查的需要。
__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()
一种面向对象的方法,使您的函数成为一个类,也称为“ 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()如果需要,在创建函子并调用该方法时,也可以将常规参数和关键字参数传递给该方法。
而且,是的,鉴于您添加到问题中的信息,这将适用。
这是一种明确的编码方法,其中已调用函数的状态保留在本地(因此避免了全局状态)。我不太喜欢其他答案中建议的非显式形式:看到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)()
如果我正确理解更新的问题,则应该可以执行以下操作
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()
raw_input()。它就在那里,因此您可以运行该程序并了解其工作原理。在我看来,您的应用程序可以更好地与回调一起使用
您在主线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()调用之后才定义变量。
您可以在代码中的许多地方重用一个简单的函数(基于此处的其他答案):
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.'
我受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
这种方法令人兴奋的部分是,它可以在任何地方使用,并且不需要工厂-它只是一个小型内存跟踪器。
根据情况,可以使用以下替代装饰器的方法:
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。
您还可以在函数前面使用标准库functools.lru_cache或functools.cache装饰器之一:
从functools导入lru_cache
@lru_cache def cheap_function():返回无