如何使用redis-py在Redis中存储复杂对象

92

hmset函数

hmset函数可以设置每个字段的值,但我发现如果该值本身是一个复杂的结构化对象,则从hget返回的值是一个序列化的字符串,而不是原始对象。

e.g

images= [{'type':'big', 'url':'....'},
     {'type':'big', 'url':'....'},
     {'type':'big', 'url':'....'}]   

redis = Redis()
redis.hset('photo:1', 'images', images)

i = redis.hget('photo:1', 'images')
print type(i)

i的类型是字符串,而不是Python对象,除了手动解析每个字段之外,是否有其他解决方法?


3
大多数答案尝试通过将复杂对象序列化为字符串来解决问题,无论是使用json还是pickle。然而,当您尝试修改复杂对象时,这种方法非常低效。相反,您可以使用redis-protobuf将嵌套数据结构保存到Redis中。请参考此答案获取示例。 - for_stack
10个回答

155

实际上,您可以使用内置模块pickle将Python对象存储在Redis中。

这是一个例子。

import pickle
import redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)
obj = ExampleObject()
pickled_object = pickle.dumps(obj)
r.set('some_key', pickled_object)
unpacked_object = pickle.loads(r.get('some_key'))
obj == unpacked_object

48
这很危险:反序列化可能会执行代码。JSON 解决方案更加健壮。 - Eric O. Lebigot
40
我不认为你的 Redis 是不可信的来源。你不能阻止人们自己给自己惹麻烦。 - Kyrylo Perevozchikov
5
@KyryloPerevozchikov:好的,很公平,因为原问题确实是在本地连接。我不会给你贴下票。然而,本地Redis可能仍然是来自不可信源的本地副本,因此我会保留我上面的评论,这并没有什么坏处。 :) - Eric O. Lebigot
4
使用cPickle比pickle更好,因为cPickle性能更优。 - Jacklynn
5
我认为在可能的情况下,JSON是首选,但仍有很多对象无法序列化成JSON。 - CivFan
显示剩余8条评论

72

如果您的数据是可JSON序列化的,则与将Python pickles保存到外部数据库相比,这可能是更好的选择,因为它是Python之外更常见的标准,本身更易于人类阅读,并避免了相当大的攻击向量

JSON示例:

import json
import redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)

images= [
    {'type':'big', 'url':'....'},
    {'type':'big', 'url':'....'},
    {'type':'big', 'url':'....'},
]

# Convert python dict to JSON str and save to Redis
json_images = json.dumps(images)
r.set('images', json_images)

# Read saved JSON str from Redis and unpack into python dict
unpacked_images = json.loads(r.get('images'))
images == unpacked_images

Python 3:

unpacked_images = json.loads(r.get('images').decode('utf-8'))
images == unpacked_images

redis.get()得到bytes对象是正常的吗?我在Python 3中得到了b'['something']' - shangsunset
7
看起来在Python3中,json.loads()函数不能很好地处理bytes对象。您需要将redis.get()返回的bytes结果解码为Python 3的str类型。请参见最新编辑。 - CivFan
4
您可以在使用StrictRedis(或版本> 3.0中的Redis)时传递decode_responses=True,这将使结果以字符串形式返回。请参考此链接获取更多信息:https://dev59.com/OWMk5IYBdhLWcg3wvARo。 - Aleksandar Jovanovic

62
你不能在Redis中创建嵌套结构,这意味着你不能(例如)在本地Redis哈希映射中存储本地Redis列表。
如果你确实需要嵌套结构,你可能希望只是存储一个JSON-blob(或类似物品)。另一个选择是将“id”/密钥存储为映射键的不同Redis对象的值,但这需要多次调用服务器才能获得完整的对象。

哦,还有一件事;使用 EVAL(服务器端 Ruby 脚本)可能可以制作一个奇怪的复合查询:http://redis.io/commands/eval - Jonatan Hedborg

9

我创建了一个库,SubRedis,它让你在Redis中创建更复杂的结构/层级。如果你提供一个Redis实例和一个前缀,它会给你一个几乎完整的独立Redis实例。

redis = Redis()
photoRedis = SubRedis("photo:%s" % photoId, redis)
photoRedis.hmset('image0', images[0])
photoRedis.hmset('image1', images[1])
...

SubRedis只是将传入的字符串作为前缀添加到平坦的redis数据结构中。我发现这是一个方便的包装器,可以用于在redis中经常进行的模式--在一些id前面添加一些数据来嵌套它们。

8

这里是一个简单的 Redis 包装器,可以对数据结构进行数据序列化和反序列化:

from redis import Redis
from collections import MutableMapping
from pickle import loads, dumps


class RedisStore(MutableMapping):

    def __init__(self, engine):
        self._store = Redis.from_url(engine)

    def __getitem__(self, key):
        return loads(self._store[dumps(key)])

    def __setitem__(self, key, value):
        self._store[dumps(key)] = dumps(value)

    def __delitem__(self, key):
        del self._store[dumps(key)]

    def __iter__(self):
        return iter(self.keys())

    def __len__(self):
        return len(self._store.keys())

    def keys(self):
        return [loads(x) for x in self._store.keys()]

    def clear(self):
        self._store.flushdb()


d = RedisStore('redis://localhost:6379/0')
d['a'] = {'b': 1, 'c': 10}
print repr(d.items())
# this will not work: (it updates a temporary copy and not the real data)
d['a']['b'] = 2
print repr(d.items())
# this is how to update sub-structures:
t = d['a']
t['b'] = 2
d['a'] = t
print repr(d.items())
del d['a']

# Here is another way to implement dict-of-dict eg d['a']['b']
d[('a', 'b')] = 1
d[('a', 'b')] = 2
print repr(d.items())
# Hopefully you do not need the equivalent of d['a']
print repr([{x[0][1]: x[1]} for x in d.items() if x[0][0] == 'a'])
del d[('a', 'b')]
del d[('a', 'c')]

如果你更喜欢在redis中使用纯文本可读数据(而不是pickle存储的二进制版本), 你可以将pickle.dumps替换为repr, 将pickle.loads替换为ast.literal_eval。对于json,使用json.dumps和json.loads。

如果你总是使用简单字符串作为键值, 你可以将pickling从键中删除。


7
你可以使用RedisLabs的RedisJSONPython客户端。它支持嵌套数据结构,非常适用于此类任务。
以下是示例代码:
   # Set the key `obj` to some object
   obj = {
       'answer': 42,
       'arr': [None, True, 3.14],
       'truth': {
           'coord': 'out there'
       }
   }
   rj.jsonset('obj', Path.rootPath(), obj)

   # Get something
   print 'Is there anybody... {}?'.format(
       rj.jsonget('obj', Path('.truth.coord'))
   )

1
另外,如果使用Docker安装Redis,请不要忘记使用ReJSON模块。这是Docker镜像:https://hub.docker.com/r/redislabs/rejson/。因为https://hub.docker.com/_/redis默认情况下没有安装ReJSON模块。 - Caio V.

6
您可以使用RedisWorks库。
运行以下命令安装: pip install redisworks
>>> from redisworks import Root
>>> root = Root()
>>> root.something = {1:"a", "b": {2: 2}}  # saves it as Hash
>>> print(root.something)  # loads it from Redis
{'b': {2: 2}, 1: 'a'}
>>> root.something['b'][2]
2

它将Python类型转换为Redis类型,反之亦然。

>>> root.sides = [10, [1, 2]]  # saves it as list in Redis.
>>> print(root.sides)  # loads it from Redis
[10, [1, 2]]
>>> type(root.sides[1])
<class 'list'>

免责声明:我编写了该库。以下是代码: https://github.com/seperman/redisworks

0
 for saving object in redis first convert object into stringify JSON
 StringifyImages = json.dumps(images)
redis.set('images', StringifyImages)

# Read stringify object from redis and parse it 
ParseImages = json.loads(redis.get('images'))

0

我最近遇到了类似的用例。在Redis哈希中存储复杂的数据结构。

我认为解决这个问题的最佳方法是将JSON对象序列化为字符串,并将其作为另一个对象的值进行存储。

Typescript示例

要存储在哈希映射表中的对象

const payload = {
 "k1":"v1",
 "k2": "v2",
 "k3": {
     "k4":"v4",
     "k5":"v5"
  }
}

将此有效载荷存储为

await redis.hmset('hashMapKey', {somePayloadKey: JSON.stringify(payload) });

这可以被检索到

      const result = await redis.hgetall('hashMapKey');
      const payload = JSON.parse(result.somePayloadKey);

hmset和hgetall是tedis中对应于redis中的HMSET和HGETALL的命令。

希望这可以帮到你。


-3

您可以将结构体存储为原样,并执行“eval”将其从字符串转换为对象:

images= [{'type':'big', 'url':'....'},
 {'type':'big', 'url':'....'},
 {'type':'big', 'url':'....'}]   
redis = Redis()
redis.hset('photo:1', 'images', images)

i = eval(redis.hget('photo:1', 'images'))
print type(i) #type of i should be list instead of string now

15
这样做是不必要的危险行为:通过 eval() 函数执行任意 Python 代码。而使用 JSON 方式则不会有这个问题。 - Eric O. Lebigot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接