Python 有两个不同的“值”的迭代器叫什么;“完成”;州?
在查询具有未知长度的分页列表的API时,我发现自己基本上是这样做的Python 有两个不同的“值”的迭代器叫什么;“完成”;州?,python,naming-conventions,Python,Naming Conventions,在查询具有未知长度的分页列表的API时,我发现自己基本上是这样做的 def fetch_one(self, n): data = json.load(urlopen(url_template % n)) if data is None: self.finished = True return for row in data: if row_is_weird(row): self.finished =
def fetch_one(self, n):
data = json.load(urlopen(url_template % n))
if data is None:
self.finished = True
return
for row in data:
if row_is_weird(row):
self.finished = True
return
yield prepare(row)
def work(self):
n = 1
self.finished = False
while not self.finished:
consume(self.fetch_one(n))
n += 1
work
和fetch_one
之间的分离使得测试非常容易,但是通过实例变量发送的信号意味着我不能同时进行多个工作,这很糟糕。我提出了一个我认为更干净的解决方案,但它涉及一个具有两个“完成”状态的迭代器,我不知道该如何称呼它。我确信这种模式在其他地方也存在,所以我希望能得到一些提示(或者这很愚蠢的原因):
然后我会像这样使用它
@thinged
def fetch_one(self, n):
data = json.load(urlopen(url_template % n))
if data is None:
raise StopThisThing()
for row in data:
if row_is_weird(row):
raise StopThisThing()
yield prepare(row)
def work(self):
n = 1
while True:
one = self.fetch_one(n)
consume(one)
if one.finished:
break
n += 1
那么我创造的东西是什么呢?我认为你可以通过创造一些特别的东西来避免这种情况
我必须构建自己的可运行示例,以展示我的意思:
def fetch_one(n):
lst = [[1,2,3], [4,5,6], [7,8,9]][n]
for x in lst:
if x == 6:
yield 'StopAll'
return
yield x
def work():
n = 0
in_progress = True
while in_progress:
numbers_iterator = fetch_one(n)
for x in numbers_iterator:
if x == 'StopAll':
in_progress = False
break
print('x =', x)
n += 1
work()
输出:
x = 1
x = 2
x = 3
x = 4
x = 5
比起self.finished或者像你建造的那样的装饰师,我更喜欢这个,但是我认为仍然可以找到更好的东西。(也许这个答案可以帮你解决这个问题)
更新:一个更简单的解决方案可能是将fetch_one
转换为带有自己的finished
标志的类
此解决方案的装饰方法可能是:
class stopper(object):
def __init__(self, func):
self.func = func
self.finished = False
def __call__(self, *args, **kwargs):
for x in self.func(*args, **kwargs):
if x == 6:
self.finished = True
raise StopIteration
yield x
else:
self.finished = True
基本上,你不再关心fetch_one的工作原理,只有在结果正常与否的情况下
用法示例:
@stopper
def fetch_one(n):
lst = [[1,2,3], [4,5,6], [7,8,9]][n]
#lst = [[1,2,3], [], [4,5,6], [7,8,9]][n] # uncomment to test for/else
for x in lst:
yield x
def work():
n = 0
while not fetch_one.finished:
for x in fetch_one(n):
print('x =', x)
n += 1
你发明的名字是“穷人版迭代器”。您的work
函数正在花费精力重新实现python在for循环中已经提供的功能。您已经得到了一系列可以随时停止的值,这正是python迭代器提供这些值的原因。我们最好将一些逻辑移到一个单独的函数中。大概是这样的:
def fetch_all(self):
for n in itertools.count():
data = json.load(urlopen(url_template % n))
if data is None:
return
for row in data:
if row_is_wierd(row):
return
yield itertools.imap(prepare, data)
from itertools import imap, count
jsonsource = imap(lambda n: json.load(urlopen(url_template % n)), count(1))
for page in pagegetter(linegetter(jsonsource, row_is_weird)):
consume(page)
或者,您可以使用异常
def fetch_all(self):
for n in itertools.count():
data = json.load(urlopen(url_template % n)
if data is None:
return
try:
yield map(prepare, data)
except WierdRowError:
return
事实上,我质疑以这种方式处理wierd行背后的逻辑。是什么让争吵变得更激烈?我们为什么停在那里?行是wierd真的是某种错误吗
在任何情况下,你的工作职能
def work():
for item in fetch_all():
consume(item)
编辑
如果有更多的信息,我会这样做
def fetch_rows():
for n in itertools.count():
data = json.load(urlopen(url_template % n))
for row in data:
if row_is_wierd(row):
return
yield row
此函数用于生成行序列
def work():
for row in fetch_all_rows():
consume(row)
此函数实际上处理行
可以用itertools中的迭代器对象替换其中的部分或全部对象。有一种更干净的方法来处理您的情况:您有一个由分页数据组成的数据源,但是可以通过检查单个行来检测终止条件。因此,我将使用迭代器逐行获取数据,并在应该时停止。无特殊值(带内或带外),无双向通信
编辑:我刚刚发现,事实上,您并不关心页面边界。在这种情况下,您只需使用:
def linegetter(url_template):
"""
Return the data line by line. Stop when end of input is detected.
"""
n=0
while True:
n += 1
data = json.load(urlopen(url_template % n))
if data is None:
return
for row in data:
if row_is_weird(row):
return
yield row
它逐行返回数据,您可以按任何方式准备和使用数据。完成了
这似乎是全部答案。但是假设您需要按页处理数据(正如您的代码现在所做的那样)。只需将第一个迭代器的输出分组为每个页面的子迭代器。代码更复杂,因为我粘贴了一个完全通用的解决方案;但是使用它真的很简单
def linegetter(source, terminate=lambda x: False):
"""
Return the data line by line, in a tuple with the page number.
Stop when end of input is detected.
"""
for n, data in enumerate(source):
if data is None:
return
for row in data:
if terminate(row):
return
yield (n, row)
def _giverow(source):
"Yield page contents line by line, discarding page number"
for page, row in source:
yield row
def pagegetter(source):
"""Return an iterator for each page of incoming data.
"""
import itertools
for it in itertools.groupby(source, lambda x : x[0]):
yield _giverow(it[1])
演示:每一行是一个数字,每一页是一个子列表。当我们看到“b”时,我们停下来。您的主循环现在没有终止检查:
incoming = iter([[1,2,3], [4,5,6, "b", 7], [7,8,9]])
def row_is_weird(r):
return r == "b"
for page in pagegetter(linegetter(incoming, row_is_weird)):
print list(page)
如您所见,代码是完全通用的。您可以将其与获取json页面的迭代器一起使用,如下所示:
def fetch_all(self):
for n in itertools.count():
data = json.load(urlopen(url_template % n))
if data is None:
return
for row in data:
if row_is_wierd(row):
return
yield itertools.imap(prepare, data)
from itertools import imap, count
jsonsource = imap(lambda n: json.load(urlopen(url_template % n)), count(1))
for page in pagegetter(linegetter(jsonsource, row_is_weird)):
consume(page)
我最初的回答是错误的;这里有一个更好的
您有几个序列(JSON文件),可以正常结束,也可以突然结束(如果需要)
行很奇怪
)。如果序列正常结束,则必须执行下一个序列。当您得到的是None
而不是JSON文件时,此序列结束。
您可以使用实例变量来表示突然结束和正常结束。这有助于代码打破深度嵌套的循环,但也会引入不需要的非局部状态
删除共享状态最简单的方法是将其作为结果或参数的一部分传递。让我们把每一行的“古怪”和它一起传递。事实上,如果一行很奇怪,我们不需要传递行值,我们只需要传递一个值,说“从现在开始,结果无效”。它有助于在正确的位置停止迭代
从本质上讲,它看起来像是被接受的答案,但在内部,您可以将其视为。附加的好处是,您永远不会将序列结束标记误认为序列标记
# preparations and mockups
input = [ # imitates rows or parsed JSON
['apple', 'orange', 'peach'], # entirely good rows
['meat', 'fowl', 'ROTTEN', 'unicorn'], # some good rows, then a bad one
['unicorn2', 'unicorn3'], # good rows we should never see
None, # sentinel imitating 'no data' from JSON parser
]
def prepare(x):
print "%s is prepared" % x
return 'prepared %s' %x
consume = lambda x: "%s is consumed" % x
row_is_weird = lambda x: x is 'ROTTEN'
# the solution
def maybe_prepare(row):
if row_is_weird(row):
return (False, None) # Nothing
else:
return (True, prepare(row)) # Just prepare(row)
def fetch_one(n):
data = input[n-1] # instead of json.load(template % n)
if data is None:
return iter([(False, None)])
else:
return (maybe_prepare(row) for row in data)
# chain_all iterates over all items of all sequences in seqs
chain_all = lambda seqs: (item for seq in seqs for item in seq)
from itertools import count
def work():
for is_ok, prepared_row in chain_all(fetch_one(n) for n in count(1)):
if not is_ok:
break
print consume(prepared_row)
这段代码仍然很容易测试,但是测试fetch_one()
要稍微复杂一些:只需在第一个(False,None)
之前迭代值。这可以通过itertools.takewhile()
轻松完成
函数maybe\u prepare()
可以是一行,但为了可读性,我将其保留为多行。不会尝试/除了one=self.fetch\u one(n)
达到同样的结果?@RikPoggi我不这么认为:在fetch_one已经产生有用的数据之后,可能会发生终止。请编辑以使示例更清楚WRT@RikPoggi的观点。。那是完全不同的情况。但是我仍然不喜欢decorator和decorred函数的耦合(一个不能脱离另一个),在您的代码中,您似乎并不真正关心数据为何完成。那么为什么不为任何终止条件简单地提出StopIteration呢?这当然会起作用,但感觉就像我做了我讨厌这个API的同样事情:带内没有更多的东西信息。另一方面,我是这个特殊产品的唯一消费者,所以…@Chipaca:是的,你在某种程度上受你试图使用的设计的约束,在它之上构建将是最简单的解决方案。您想要什么样的API?不同的设计可以使用线程,但我不知道最终是否值得;这个