Python 迭代器来屏蔽API的多个页面

Python 迭代器来屏蔽API的多个页面,python,rest,Python,Rest,我正在用python为一些API构建一个API客户机,当数据包含在多个页面中时,它提供以下数据布局: { "data":["some","pieces","of","data], "results_per_page=2500, "total_results": 10000 "next_url": "http://fullyqualifiedurl.com/results_after=5000" "previous_url": "http://fullyqu

我正在用python为一些API构建一个API客户机,当数据包含在多个页面中时,它提供以下数据布局:

{
    "data":["some","pieces","of","data],
    "results_per_page=2500,
    "total_results": 10000
    "next_url": "http://fullyqualifiedurl.com/results_after=5000"
    "previous_url": "http://fullyqualifiedurl.com/results_after=2500
}
我想要一个迭代器,客户端可以这样调用它:

>>> results = client.results()
>>> result_count = 0
>>> for result in results:
>>>     result_count += 1
>>> print(result_count)
10000
在这种情况下,迭代器在到达其当前页面的末尾时以静默方式请求新页面数据

我已经开发了一些可以生成页面的东西,但是在后续调用中,我不希望必须重新获取数据。以下是我所拥有的:

Class Iterator:
    def __init__(self, current_page, max_results=None):
        self.current_page = current_page
        self.max_results = max_results
        self.yielded_count = 0

    def _iter_items(self):
        for page in self._iter_page():
            for item in page:
                # early break from page if we have set a limit.
                if self._limit_reached():
                    raise StopIteration
                self.yielded_count += 1
                yield item

    def _iter_page(self):
        while self.current_page is not None:
            yield self.current_page
            if self._has_next_page():
                self.current_page = self._get_next_page()
            else:
                self.current_page = None

    def __iter__(self):
        return self._iter_items()

    def __next__(self):
        return next(self._iter_items())

    def _iter_page(self):
        while self.current_page is not None:
            yield self.current_page
            if self._has_next_page():
                self.current_page = self._get_next_page()
            else:
                self.current_page = None

    def _get_next_page(self):
        if self.current_page.next_page_url:
            return self.api_request(self.current_page.next_page_url)
        else:
            return None

    def _keep_iterating(self):
        return (
            self.current_page is not None
            and self.max_results
            and self.yielded_count >= self.max_results
    )

    def _limit_reached(self):
        return self.max_results and self.yielded_count >= self.max_results

class Page:

    def __init__(self, json_data, *args, **kwargs):
        self.client = kwargs.get("client")
        self.next_page_url = json_data["pages"]["next_url"]
        self.previous_page_url = json_data["pages"]["previous_url"]
        self.total_count = json_data["total_count"]
        self._data_iterator = iter(datum for datum in json_data["data"])

    def __iter__(self):
        return self

    def __next__(self):
        item = next(self._data_iterator)
        return item

现在发生的是,我可以成功地对它迭代一次,但是在第二次迭代时,迭代器是空的。我希望它在第一次搜索时缓存结果,并允许后续迭代。我是不是走错了路?我觉得这应该有一个既定的模式,但实际上找不到任何东西。

我不确定您在这里谈论的是
页面
类型还是
迭代器
类型,因为它们都是迭代器,都有相同的问题,你只给了我们一个模糊的描述,说明你在和哪个人做什么。但以下所有内容都同样适用于它们中的任何一个(除了一个注释),因此我将讨论
Page
,因为这是最简单的一个


迭代器只能使用一次。这是迭代器的固有含义

您可以使用拆分第二个迭代器,该迭代器缓存第一个迭代器中的值

但是,如果您的目标是反复迭代相同的值,那么有一个简单得多的解决方案:只需将迭代器复制到序列中,如
列表
元组
,然后您可以根据需要多次迭代

page = list(Page(data, …))
for thing in page:
    print(thing)
for thing in page:
    print(thing)

当我们进行此操作时,您的
迭代器
不是有效的迭代器:

def __iter__(self):
    return self._iter_items()

def __next__(self):
    return next(self._iter_items())
迭代器必须从
返回
self
,就像
页面
那样。Python没有强制执行该规则,因此如果您弄错了,您通常会在一个测试中得到一些似乎有效的结果,但在其他地方却做了错误的事情


或者……您确定要将
Page
作为迭代器,而不是可重用、非迭代器

class Page:

    def __init__(self, json_data, *args, **kwargs):
        self.client = kwargs.get("client")
        self.next_page_url = json_data["pages"]["next_url"]
        self.previous_page_url = json_data["pages"]["previous_url"]
        self.total_count = json_data["total_count"]

    def __iter__(self):
        return iter(datum for datum in json_data["data"])
现在,您不需要将数据复制到
列表中
,除非您希望执行列表y操作,如按随机顺序索引数据:

page = Page(data, …)
for thing in page:
    print(thing)
for thing in page:
    print(thing)

作为旁注,这是重复的:

iter(datum for datum in json_data["data"])
(json_数据[“数据”]中的数据的数据)
json_数据[“数据”]
一样,包装在生成器表达式中。由于生成器表达式已经是迭代器,因此您只需返回它:

return (datum for datum in json_data["data"])
或者,更简单的是,您可以在数据上返回一个迭代器:

return iter(json_data["data"])

如果你真的想要列表y序列行为,你甚至可以很容易地把它变成一个完整的序列:

class Page:

    def __init__(self, json_data, *args, **kwargs):
        self.client = kwargs.get("client")
        self.next_page_url = json_data["pages"]["next_url"]
        self.previous_page_url = json_data["pages"]["previous_url"]
        self.total_count = json_data["total_count"]

    def __len__(self):
        return len(json_data["data"])

    def __getitem__(self, index):
        return json_data["data"][index]
现在:

page = Page(data, …)
for thing in page:
    print(thing)
for thing in reversed(page):
    print(thing)
for thing in page[-6:-2]:
    print(thing)

我不确定您在这里谈论的是
页面
类型还是
迭代器
类型,因为它们都是迭代器,并且都有相同的问题,而您只对您使用的任何一个进行了模糊的描述。但以下所有内容都同样适用于它们中的任何一个(除了一个注释),因此我将讨论
Page
,因为这是最简单的一个


迭代器只能使用一次。这是迭代器的固有含义

您可以使用拆分第二个迭代器,该迭代器缓存第一个迭代器中的值

但是,如果您的目标是反复迭代相同的值,那么有一个简单得多的解决方案:只需将迭代器复制到序列中,如
列表
元组
,然后您可以根据需要多次迭代

page = list(Page(data, …))
for thing in page:
    print(thing)
for thing in page:
    print(thing)

当我们进行此操作时,您的
迭代器
不是有效的迭代器:

def __iter__(self):
    return self._iter_items()

def __next__(self):
    return next(self._iter_items())
迭代器必须从
返回
self
,就像
页面
那样。Python没有强制执行该规则,因此如果您弄错了,您通常会在一个测试中得到一些似乎有效的结果,但在其他地方却做了错误的事情


或者……您确定要将
Page
作为迭代器,而不是可重用、非迭代器

class Page:

    def __init__(self, json_data, *args, **kwargs):
        self.client = kwargs.get("client")
        self.next_page_url = json_data["pages"]["next_url"]
        self.previous_page_url = json_data["pages"]["previous_url"]
        self.total_count = json_data["total_count"]

    def __iter__(self):
        return iter(datum for datum in json_data["data"])
现在,您不需要将数据复制到
列表中
,除非您希望执行列表y操作,如按随机顺序索引数据:

page = Page(data, …)
for thing in page:
    print(thing)
for thing in page:
    print(thing)

作为旁注,这是重复的:

iter(datum for datum in json_data["data"])
(json_数据[“数据”]中的数据的数据)
json_数据[“数据”]
一样,包装在生成器表达式中。由于生成器表达式已经是迭代器,因此您只需返回它:

return (datum for datum in json_data["data"])
或者,更简单的是,您可以在数据上返回一个迭代器:

return iter(json_data["data"])

如果你真的想要列表y序列行为,你甚至可以很容易地把它变成一个完整的序列:

class Page:

    def __init__(self, json_data, *args, **kwargs):
        self.client = kwargs.get("client")
        self.next_page_url = json_data["pages"]["next_url"]
        self.previous_page_url = json_data["pages"]["previous_url"]
        self.total_count = json_data["total_count"]

    def __len__(self):
        return len(json_data["data"])

    def __getitem__(self, index):
        return json_data["data"][index]
现在:

page = Page(data, …)
for thing in page:
    print(thing)
for thing in reversed(page):
    print(thing)
for thing in page[-6:-2]:
    print(thing)

谢谢你提供的所有信息!关于迭代器在
\uuuu iter\uuuu()
中不返回
self
的无效性,我实际上是在谷歌的核心API中找到了一些东西:。无效是因为我没有检查是否已经开始?或者谷歌在这里所做的被认为是糟糕的做法?@tadgeh该类没有
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。这使得迭代器的名称非常奇怪,而且有点误导,但从技术上讲它并没有错。谢谢所有的信息!关于迭代器在
\uuuu iter\uuuu()
中不返回
self
的无效性,我实际上是在谷歌的核心API中找到了一些东西:。无效是因为我没有检查是否已经开始?或者谷歌在这里所做的被认为是糟糕的做法?@tadgeh该类没有
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。这使得迭代器的名称非常奇怪,而且有点误导,但从技术上讲它并没有错。