目前,我正在做类似下面的事情,这很乏味:
run_once = 0
while 1:
if run_once == 0:
myFunction()
run_once = 1:
我猜是有一些更可接受的方法来处理这些东西?
我正在寻找的是按需执行一次函数。例如,按下某个按钮。它是一个交互式应用程序,具有许多用户控制的开关。每个交换机都有一个垃圾变量,只是为了跟踪它是否已经运行,似乎效率低下。
答案 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(): 不返回
答案 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)
我不确定我是否理解你的问题,但我认为你可以分开循环。在函数和没有它的部分,保存两个循环。