给定 NDB 游标,获取上一页结果的正确方法是啥?
Posted
技术标签:
【中文标题】给定 NDB 游标,获取上一页结果的正确方法是啥?【英文标题】:What is the correct way to get the previous page of results given an NDB cursor?给定 NDB 游标,获取上一页结果的正确方法是什么? 【发布时间】:2014-02-04 13:47:15 【问题描述】:我正在努力通过 GAE 提供一个 API,它允许用户在一组实体中向前和向后翻页。我查看了section about cursors on the NDB Queries documentation page,其中包含一些示例代码,描述了如何通过查询结果向后翻页,但它似乎没有按预期工作。我正在使用 GAE 开发 SDK 1.8.8。
这是该示例的修改版本,它创建 5 个示例实体,获取并打印第一页,前进并打印第二页,然后尝试后退并再次打印第一页:
import pprint
from google.appengine.ext import ndb
class Bar(ndb.Model):
foo = ndb.StringProperty()
#ndb.put_multi([Bar(foo="a"), Bar(foo="b"), Bar(foo="c"), Bar(foo="d"), Bar(foo="e")])
# Set up.
q = Bar.query()
q_forward = q.order(Bar.foo)
q_reverse = q.order(-Bar.foo)
# Fetch the first page.
bars1, cursor1, more1 = q_forward.fetch_page(2)
pprint.pprint(bars1)
# Fetch the next (2nd) page.
bars2, cursor2, more2 = q_forward.fetch_page(2, start_cursor=cursor1)
pprint.pprint(bars2)
# Fetch the previous page.
rev_cursor2 = cursor2.reversed()
bars3, cursor3, more3 = q_reverse.fetch_page(2, start_cursor=rev_cursor2)
pprint.pprint(bars3)
(仅供参考,您可以在本地应用引擎的交互式控制台中运行上述内容。)
上面的代码打印出以下结果;请注意,结果的第三页只是反转了第二页,而不是回到第一页:
[Bar(key=Key('Bar', 4996180836614144), foo=u'a'),
Bar(key=Key('Bar', 6122080743456768), foo=u'b')]
[Bar(key=Key('Bar', 5559130790035456), foo=u'c'),
Bar(key=Key('Bar', 6685030696878080), foo=u'd')]
[Bar(key=Key('Bar', 6685030696878080), foo=u'd'),
Bar(key=Key('Bar', 5559130790035456), foo=u'c')]
我期待看到这样的结果:
[Bar(key=Key('Bar', 4996180836614144), foo=u'a'),
Bar(key=Key('Bar', 6122080743456768), foo=u'b')]
[Bar(key=Key('Bar', 5559130790035456), foo=u'c'),
Bar(key=Key('Bar', 6685030696878080), foo=u'd')]
[Bar(key=Key('Bar', 6685030696878080), foo=u'a'),
Bar(key=Key('Bar', 5559130790035456), foo=u'b')]
如果我将代码的“获取上一页”部分更改为以下代码 sn-p,我会得到预期的输出,但是使用前向排序查询和 end_cursor 是否有任何我没有预见到的缺点而不是文档中描述的机制?
# Fetch the previous page.
bars3, cursor3, more3 = q_forward.fetch_page(2, end_cursor=cursor1)
pprint.pprint(bars3)
【问题讨论】:
***.com/questions/14543008/… --> 同样的问题 谢谢@zho。在发布问题之前,我阅读了所有与使用 ndb 游标进行反向分页相关的问题,包括您的问题。它们是类似的问题,但我认为我的示例与您的问题没有相同的问题(反转已经反转的光标)。我还尝试将我的示例归结为可以在交互式控制台中运行的内容,并将 Web 框架排除在外。 看起来像 end_cursor 的解决方案将始终只显示第一页。尝试从第三页获取第二页。 (对我不起作用)。 【参考方案1】:为了使文档中的示例更清晰一些,让我们暂时忘记数据存储区,而是使用列表:
# some_list = [4, 6, 1, 12, 15, 0, 3, 7, 10, 11, 8, 2, 9, 14, 5, 13]
# Set up.
q = Bar.query()
q_forward = q.order(Bar.key)
# This puts the elements of our list into the following order:
# ordered_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
q_reverse = q.order(-Bar.key)
# Now we reversed the order for backwards paging:
# reversed_list = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# Fetch a page going forward.
bars, cursor, more = q_forward.fetch_page(10)
# This fetches the first 10 elements from ordered_list(!)
# and yields the following:
# bars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# cursor = [... 9, CURSOR-> 10 ...]
# more = True
# Please notice the right-facing cursor.
# Fetch the same page going backward.
rev_cursor = cursor.reversed()
# Now the cursor is facing to the left:
# rev_cursor = [... 9, <-CURSOR 10 ...]
bars1, cursor1, more1 = q_reverse.fetch_page(10, start_cursor=rev_cursor)
# This uses reversed_list(!), starts at rev_cursor and fetches
# the first ten elements to it's left:
# bars1 = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
因此,文档中的示例以两个不同的顺序从两个不同的方向获取同一页面。这不是你想要达到的。
您似乎已经找到了一个很好地涵盖您的用例的解决方案,但让我建议另一个:
只需重用 cursor1 即可返回 page2。 如果我们在谈论前端并且当前页面是 page3,这意味着将 cursor3 分配给“下一个”按钮,将 cursor1 分配给“上一个”按钮。
这样您既不需要反转查询也不需要反转光标。
【讨论】:
这很有帮助,但我希望有一个使用单个光标的解决方案,这样我就不需要跟踪 Web 请求中的任何其他状态。我认为如果用户多次前进,我需要保留一堆“后退”光标,以便能够让他们多次返回 - 我理解正确吗? 当您进行反向查询时,这将返回一个新游标(在本例中为 cursor1)。此光标指向bars1 中0 之后的点。然后,您可以使用此光标返回另一页。【参考方案2】:我冒昧地将 Bar
模型更改为 Character
模型。该示例看起来更像 Pythonic IMO ;-)
我写了一个快速的单元测试来演示分页,准备复制粘贴:
import unittest
from google.appengine.datastore import datastore_stub_util
from google.appengine.ext import ndb
from google.appengine.ext import testbed
class Character(ndb.Model):
name = ndb.StringProperty()
class PaginationTest(unittest.TestCase):
def setUp(self):
tb = testbed.Testbed()
tb.activate()
self.addCleanup(tb.deactivate)
tb.init_memcache_stub()
policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(
probability=1)
tb.init_datastore_v3_stub(consistency_policy=policy)
characters = [
Character(id=1, name='Luigi Vercotti'),
Character(id=2, name='Arthur Nudge'),
Character(id=3, name='Harry Bagot'),
Character(id=4, name='Eric Praline'),
Character(id=5, name='Ron Obvious'),
Character(id=6, name='Arthur Wensleydale')]
ndb.put_multi(characters)
query = Character.query().order(Character.key)
# Fetch second page
self.page = query.fetch_page(2, offset=2)
def test_current_page(self):
characters, _cursor, more = self.page
self.assertSequenceEqual(
['Harry Bagot', 'Eric Praline'],
[character.name for character in characters])
self.assertTrue(more)
def test_next_page(self):
_characters, cursor, _more = self.page
query = Character.query().order(Character.key)
characters, cursor, more = query.fetch_page(2, start_cursor=cursor)
self.assertSequenceEqual(
['Ron Obvious', 'Arthur Wensleydale'],
[character.name for character in characters])
self.assertFalse(more)
def test_previous_page(self):
_characters, cursor, _more = self.page
# Reverse the cursor (point it backwards).
cursor = cursor.reversed()
# Also reverse the query order.
query = Character.query().order(-Character.key)
# Fetch with an offset equal to the previous page size.
characters, cursor, more = query.fetch_page(
2, start_cursor=cursor, offset=2)
# Reverse the results (undo the query reverse ordering).
characters.reverse()
self.assertSequenceEqual(
['Luigi Vercotti', 'Arthur Nudge'],
[character.name for character in characters])
self.assertFalse(more)
一些解释:
setUp
方法首先初始化所需的存根。然后将 6 个示例字符与 id 放在一起,因此顺序不是随机的。因为有 6 个字符,所以我们有 3 页 2 个字符。第二页直接使用有序查询和偏移量 2 获取。注意偏移量,这是示例的关键。
test_current_page
验证是否提取了中间的两个字符。为了便于阅读,字符按名称进行比较。 ;-)
test_next_page
获取下一页(第三页)并验证预期字符的名称。到目前为止,一切都很简单。
现在test_previous_page
很有趣。这做了几件事,首先将光标反转,因此光标现在指向向后而不是向前。 (这提高了可读性,没有它也可以工作,但是偏移量会有所不同,我将把它作为练习留给读者。)接下来创建一个反向排序的查询,这是必要的,因为偏移量不能为负并且您想要拥有以前的实体。然后使用等于当前页面的页面长度的偏移量获取结果。否则查询将返回相同的结果,但相反(如问题中所示)。现在因为查询是反向排序的,所以结果都是向后的。我们只是简单地反转结果列表来解决这个问题。最后但并非最不重要的一点是,声明了预期的名称。
旁注:由于这涉及全局查询,因此概率设置为 100%,因此在生产中(因为最终的一致性)在之后放置和查询很可能会失败。
【讨论】:
谢谢。我必须说,ndb 中游标的实现是相当不称职的。您必须使用抵消和反转结果的事实......有些人特别擅长让简单的事情最终变得混乱。感谢您的帮助。以上是关于给定 NDB 游标,获取上一页结果的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章