示例:构建数据库迭代器
SCAN
命令虽然可以以迭代的形式访问数据库,但它使用起来并不是特别方便,比如说:
SCAN
命令每次迭代都会返回一个游标,而用户需要手动地将这个游标用作下次迭代时的输入参数,如果用户不小心丢失或者弄错了这个游标的话,那么就可能会给迭代带来错误或者麻烦。SCAN
命令每次都会返回一个包含两个元素的结果,其中第一个元素为游标,而第二个元素才是当前被迭代的键,如果迭代器能够直接返回被迭代的键,那么它使用起来就会更加方便。
为了解决以上这两个问题,我们可以在 SCAN
命令的基础上进行一些修改,实现出代码清单 11-1 所示的迭代器:这个迭代器不仅会自动记录每次迭代的游标以防丢失,它还可以直接返回被迭代的数据库键以供用户使用。
代码清单 11-1 数据库迭代器:/database/db_iterator.py
- class DbIterator:
- def __init__(self, client, match=None, count=None):
- """
- 创建一个新的迭代器。
- 可选的 match 参数用于指定迭代的匹配模式,
- 而可选的 count 参数则用于指定我们期待每次迭代能够返回的键数量。
- """
- self.client = client
- self.match = match
- self.count = count
- # 当前迭代游标
- self.current_cursor = 0
- # 记录迭代是否已经完成的状态变量
- self.iteration_is_over = False
- def next(self):
- """
- 以列表形式返回当前被迭代到的数据库键,
- 返回 None 则表示本次迭代已经完成。
- """
- if self.iteration_is_over:
- return None
- # 获取下次迭代的游标以及当前被迭代的数据库键
- next_cursor, keys = self.client.scan(self.current_cursor, self.match, self.count)
- # 如果下次迭代的游标为 0 ,那么表示迭代已完成
- if next_cursor == 0:
- self.iteration_is_over = True
- # 更新游标
- self.current_cursor = next_cursor
- # 返回当前被迭代的数据库键
- return keys
作为例子,以下代码展示了如何使用这个迭代器去迭代一个数据库:
- >>> from redis import Redis
- >>> from db_iterator import DbIterator
- >>> client = Redis(decode_responses=True)
- >>> for i in range(50): # 向数据库插入 50 个键
- ... key = "key{0}".format(i)
- ... value = i
- ... client.set(key, value)
- ...
- True
- True
- ...
- True
- >>> iterator = DbIterator(client)
- >>> iterator.next() # 开始迭代
- ['key46', 'key1', 'key27', 'key39', 'key15', 'key0', 'key43', 'key12', 'key49', 'key41', 'key10']
- >>> iterator.next()
- ['key23', 'key7', 'key9', 'key20', 'key18', 'key3', 'key5', 'key34', 'key32', 'key40']
- >>> iterator.next()
- ['key4', 'key33', 'key30', 'key45', 'key38', 'key31', 'key6', 'key16', 'key25', 'key14', 'key13']
- >>> iterator.next()
- ['key29', 'key2', 'key42', 'key11', 'key48', 'key28', 'key8', 'key44', 'key21', 'key26']
- >>> iterator.next()
- ['key22', 'key47', 'key36', 'key17', 'key19', 'key24', 'key35', 'key37']
- >>> iterator.next() # 迭代结束
- >>>
注解
redis-py 提供的迭代器
实际上,redis-py 客户端也为 SCAN
命令实现了一个迭代器 ——用户只需要调用 redis-py 的 scan_iter()
方法,就会得到一个 Python 迭代器,然后就可以通过这个迭代器对数据库中的键进行迭代:
- scan_iter(self, match=None, count=None) unbound redis.client.Redis method
- Make an iterator using the SCAN command so that the client doesn't
- need to remember the cursor position.
- ``match`` allows for filtering the keys by pattern
- ``count`` allows for hint the minimum number of returns
redis-py 提供的迭代器跟 DbIterator
一样,都可以让用户免去手动输入游标的麻烦,但它们之间也有不少区别:
redis-py 的迭代器每次迭代只返回一个元素。
因为 redis-py 的迭代器是通过 Python 的迭代器特性实现的,所以用户可以直接以
for key in redis.scan_iter()
的形式进行迭代。(DbIterator
实际上也可以实现这样的特性,但是由于 Python 迭代器的相关知识并不在本书的介绍范围之内,所以我们这个自制的迭代器才没有配备这一特性。)redis-py 的迭代器也拥有
next()
方法,但这个方法每次被调用时只会返回单个元素,并且它在所有元素都被迭代完毕时将抛出一个StopIteration
异常。
以下是一个 redis-py 迭代器的使用示例:
- >>> from redis import Redis
- >>> client = Redis(decode_responses=True)
- >>> client.mset({"k1":"v1", "k2":"v2", "k3":"v3"})
- True
- >>> for key in client.scan_iter():
- ... print(key)
- ...
- k1
- k3
- k2
因为 redis-py 为 scan_iter()
提供了直接支持,它比需要额外引入的 DbIterator
更为方便一些,所以本书之后展示的所有迭代程序都将使用 scan_iter()
而不是 DbIterator
。不过由于这两个迭代器的底层实现是相仿的,所以使用哪个其实差别都并不大。