我有一个python
函数(称之为myFunction
),它作为输入一个数字列表,并且在复杂计算后,返回结果计算(数字)。
该功能如下所示:
def myFunction( listNumbers ):
# initialize the result of the calculation
calcResult = 0
# looping through all indices, from 0 to the last one
for i in xrange(0, len(listNumbers), 1):
# some complex calculation goes here, changing the value of 'calcResult'
# let us now return the result of the calculation
return calcResult
我测试了这个功能,它按预期工作。
通常情况下,myFunction
会提供一个listNumbers
参数,其中包含 5,000,000 个元素。正如您所料,计算需要时间。我需要这个功能尽可能快地运行
接下来是挑战:假设现在时间是凌晨5点,listNumbers
中只包含 4,999,999 值。意思是,它的最后一个值尚不可用。此值仅在早上6点时可用。
显然,我们可以执行以下操作(第一模式):等到早上6点。然后,将最后一个值附加到listNumbers
,然后运行myFunction
。这个解决方案有效,但需要一段时间才能myFunction
返回我们的计算结果(因为我们需要从第一个元素处理整个数字列表上)。请记住,我们的目标是在早上6点之后尽快获得结果。
我正在考虑一种更有效的解决方法(第二模式):因为(早上5点)我们有listNumbers
,其中 4,999,999 值,让我们立即开始运行myFunction
。让我们处理尽我们所能(记住,我们还没有最后一段数据),然后 - 恰好在早上6点 - “插入”新数据 - 并生成计算结果。这应该明显更快,因为大部分处理将在早上6点之前完成,因此,我们只需要处理新数据 - 这意味着计算结果应立即可用早上6点以后。
我们假设没有办法让我们检查myFunction
的代码或修改它。是否有任何编程技巧/设计理念,我们可以myFunction
原样,并使用它做一些事情(不更改其代码),以便我们可以让它在第二模式中运行,而不是第一模式?
请不要建议使用c++
/ numpy + cython
/ parallel computing
等来解决此问题。这里的目标是查看是否有任何编程技术或设计模式可以轻松用于解决此类问题。
答案 0 :(得分:10)
您可以使用generator作为输入。只有在有可供处理的数据时,生成器才会返回。
更新:感谢您的精彩评论,我想删除此条目:)
class lazylist(object):
def __init__(self):
self.cnt = 0
self.length = 5000000
def __iter__(self):
return self
def __len__(self):
return self.length
def next(self):
if self.cnt < self.length:
self.cnt += 1
#return data here or wait for it
return self.cnt #just return a counter for this example
else:
raise StopIteration()
def __getitem__(self, i):
#again, block till you have data.
return i+1 #simple counter
myFunction(lazylist())
更新:从评论和其他解决方案中可以看出,你的循环结构和len
调用引起了很多麻烦,如果你可以消除它,你可以使用更优雅的解决方案。 for e in li
或enumerate
是pythonic的方式。
答案 1 :(得分:5)
按“数字列表”,您的意思是实际的内置list
类型吗?
如果没有,那很简单。 Python使用duck-typing,因此传递任何支持迭代的序列都可以。使用yield
关键字传递generator。
def delayed_list():
for val in numpy_array[:4999999]:
yield val
wait_until_6am()
yield numpy_array[4999999]
然后,
myFunction(delayed_list())
另外,请查看PEP8以获取推荐的Python代码样式:
my_function
代替myFunction
for i, val in enumerate(numbers):
代替for i in xrange(0, len(listNumbers), 1):
等。答案 2 :(得分:4)
子类列表,以便当函数尝试读取它阻塞的最后一个值,直到另一个线程提供该值。
import threading
import time
class lastblocks(list):
def __init__(self,*args,**kwargs):
list.__init__(self,*args,**kwargs)
self.e = threading.Event()
def __getitem__(self, index):
v1 = list.__getitem__(self,index)
if index == len(self)-1:
self.e.wait()
v2 = list.__getitem__(self,index)
return v2
else:
return v1
l = lastblocks(range(5000000-1)+[None])
def reader(l):
s = 0
for i in xrange(len(l)):
s += l[i]
print s
def writer(l):
time.sleep(10)
l[5000000-1]=5000000-1
l.e.set()
print "written"
reader = threading.Thread(target=reader, args=(l,))
writer = threading.Thread(target=writer, args=(l,))
reader.start()
writer.start()
打印:
written
12499997500000
for numpy:
import threading
import time
import numpy as np
class lastblocks(np.ndarray):
def __new__(cls, arry):
obj = np.asarray(arry).view(cls)
obj.e = threading.Event()
return obj
def __array_finalize__(self, obj):
if obj is None: return
self.e = getattr(obj, 'e', None)
def __getitem__(self, index):
v1 = np.ndarray.__getitem__(self,index)
if index == len(self)-1:
self.e.wait()
v2 = np.ndarray.__getitem__(self,index)
return v2
else:
return v1
l = lastblocks(np.asarray(range(5000000-1)+[None]))
def reader(l):
s = 0
for i in xrange(len(l)):
s += l[i]
print s
def writer(l):
time.sleep(10)
l[5000000-1]=5000000-1
l.e.set()
print "written"
reader = threading.Thread(target=reader, args=(l,))
writer = threading.Thread(target=writer, args=(l,))
reader.start()
writer.start()
答案 3 :(得分:1)
Memory protection barriers是解决此类问题的一般方法。
内存屏障是一种硬件功能,当程序试图访问禁止的内存区域(通常在页面级别可控)时会导致中断。然后,中断处理程序可以采取适当的操作,例如暂停程序直到数据准备就绪。
因此,在这种情况下,您将在列表的最后一页设置一个屏障,中断处理程序将等到06:00,然后才允许程序继续。
答案 4 :(得分:1)
您可以创建自己的迭代器来迭代5,000,000个元素。这样可以做任何你需要做的事情来等待最后的元素(不能具体,因为问题中的例子相当抽象)。我假设您不关心代码挂到6:00,或者知道如何在后台线程中执行此操作。
有关编写自己的迭代器的更多信息位于http://docs.python.org/library/stdtypes.html#iterator-types
答案 5 :(得分:1)
我对于无法调查myFunction
感到有些困惑。至少你必须知道你的列表是否被索引迭代或访问。您的示例可能会建议使用索引。如果要利用迭代器/生成器,则必须进行迭代。我知道你说myFunction
是不可更改的,但只是想指出,大多数pythonic版本将是:
def myFunction( listNumbers ):
calcResult = 0
# enumerate if you really need an index of element in array
for n,v in enumerate(listNumbers):
# some complex calculation goes here, changing the value of 'calcResult'
return calcResult
现在你可以开始介绍好主意了。一个可能是使用您自己的类型包装列表并提供__iter__
方法(作为生成器);你可以返回值,如果可以访问,等待更多数据,如果你期望任何数据或在屈服最后一个元素后返回。
如果您必须按索引访问列表,则可以使用{D}中的__getitem__
示例。但它有一个限制,你必须提前知道数组的大小。
答案 6 :(得分:1)
有一个更简单的生成器解决方案:
def fnc(lst):
result = 0
index = 0
while index < len(lst):
while index < len(lst):
... do some manipulations here ...
index += 1
yield result
lst = [1, 2, 3]
gen = fnc(lst)
print gen.next()
lst.append(4)
print gen.next()
答案 7 :(得分:0)
难道你不能简单地做这样的事情:
processedBefore6 = myFunction([1,2,3]) # the first 4,999,999 vals.
while lastVal.notavailable:
sleep(1)
processedAfter6 = myFunction([processedBefore6, lastVal])
如果效果是线性的(步骤1 - >步骤2 - >步骤3等),这应该允许您预先做尽可能多的工作,然后在可用时捕获最终值并完成。