示例:使用散列键重新实现文章储存程序

在稍早之前,我们用散列重写了《字符串》一章介绍过的计数器程序,但是除了计数器程序之外,还有另一个程序也非常适合使用散列来重写,那就是文章数据储存程序:比起用多个字符串键来储存文章的各项数据,更好的做法是把每篇文章的所有数据都储存到同一个散列里面,代码清单 3-6 展示了这一想法的具体实现。


代码清单 3-6 使用散列实现的文章数据储存程序:/hash/article.py

  1. from time import time
  2.  
  3. class Article:
  4.  
  5. def __init__(self, client, article_id):
  6. self.client = client
  7. self.article_id = str(article_id)
  8. self.article_hash = "article::" + self.article_id
  9.  
  10. def is_exists(self):
  11. """
  12. 检查给定 ID 对应的文章是否存在。
  13. """
  14. # 如果文章散列里面已经设置了标题,那么我们认为这篇文章存在
  15. return self.client.hexists(self.article_hash, "title")
  16.  
  17. def create(self, title, content, author):
  18. """
  19. 创建一篇新文章,创建成功时返回 True ,
  20. 因为文章已经存在而导致创建失败时返回 False 。
  21. """
  22. # 文章已存在,放弃执行创建操作
  23. if self.is_exists():
  24. return False
  25.  
  26. # 把所有文章数据都放到字典里面
  27. article_data = {
  28. "title": title,
  29. "content": content,
  30. "author": author,
  31. "create_at": time()
  32. }
  33. # redis-py 的 hmset() 方法接受一个字典作为参数,
  34. # 并根据字典内的键和值对散列的字段和值进行设置。
  35. return self.client.hmset(self.article_hash, article_data)
  36.  
  37. def get(self):
  38. """
  39. 返回文章的各项信息。
  40. """
  41. # hgetall() 方法会返回一个包含标题、内容、作者和创建日期的字典
  42. article_data = self.client.hgetall(self.article_hash)
  43. # 把文章 ID 也放到字典里面,以便用户操作
  44. article_data["id"] = self.article_id
  45. return article_data
  46.  
  47. def update(self, title=None, content=None, author=None):
  48. """
  49. 对文章的各项信息进行更新,
  50. 更新成功时返回 True ,失败时返回 False 。
  51. """
  52. # 如果文章并不存在,那么放弃执行更新操作
  53. if not self.is_exists():
  54. return False
  55.  
  56. article_data = {}
  57. if title is not None:
  58. article_data["title"] = title
  59. if content is not None:
  60. article_data["content"] = content
  61. if author is not None:
  62. article_data["author"] = author
  63. return self.client.hmset(self.article_hash, article_data)

新的文章储存程序除了会用到散列之外,还有两个需要注意的地方:

  • 虽然 Redis 为字符串提供了 MSET 命令和 MSETNX 命令,但是却并没有为散列提供 HMSET 命令对应的 HMSETNX 命令,所以这个程序在创建一篇新文章之前,需要先通过 is_exists() 方法检查文章是否存在,然后再考虑是否使用 HMSET 命令去进行设置。

  • 在使用字符串键储存文章数据的时候,为了避免数据库中出现键名冲突,程序必须为每篇文章的每个属性都设置一个独一无二的键,比如使用 article::10086::title 键去储存 ID 为 10086 的文章的标题,使用 article::12345::title 键去储存 ID 为 12345 的文章的标题,诸如此类。相反地,因为新的文章储存程序可以直接将一篇文章的所有相关信息都储存到同一个散列里面,所以它可以直接在散列里面使用 title 作为标题的字段,而不必担心出现命名冲突。

以下代码简单地展示了这个文章储存程序的使用方法:

  1. >>> from redis import Redis
  2. >>> from article import Article
  3. >>>
  4. >>> client = Redis(decode_responses=True)
  5. >>> article = Article(client, 10086)
  6. >>>
  7. >>> # 创建文章
  8. >>> article.create("greeting", "hello world", "peter")
  9. >>>
  10. >>> # 获取文章内容
  11. >>> article.get()
  12. {'content': 'hello world', 'id': '10086', 'created_at': '1442744762.631885', 'title': 'greeting', 'author': 'peter'}
  13. >>>
  14. >>> # 检查文章是否存在
  15. >>> article.is_exists()
  16. True
  17. >>> # 更新文章内容
  18. >>> article.update(content="good morning!")
  19. >>> article.get()
  20. {'content': 'good morning!', 'id': '10086', 'created_at': '1442744762.631885', 'title': 'greeting', 'author': 'peter'}

图 3-22 以图形方式展示了这段代码创建的散列键。


图 3-22 储存在散列里面的文章数据_images/IMAGE_ARTICLE_IN_HASH.png