【问题标题】:Getting values with the right type in Redis在 Redis 中获取正确类型的值
【发布时间】:2012-10-15 04:00:31
【问题描述】:

我在我的 python 应用程序中使用 redis 来存储简单的值,例如计数器和时间戳列表,但是我试图获取一个计数器并将它与一个数字进行比较,我遇到了一个问题。

如果我这样做:

import redis
...
myserver = redis.Redis("localhost")
myserver.set('counter', 5)

然后尝试像这样获取该值:

if myserver.get('counter') < 10:
     myserver.incr('counter')

然后我在 if 语句中得到一个类型错误,因为我在比较 '5' 我正在存储一个整数值并获得一个字符串 1(可以考虑作为不同的值)。

我的问题是:这应该像那样工作吗?我的意思是它是一个非常基本的类型,我知道我是否必须解析对象但一个 int?好像我做错了什么。

我缺少什么配置吗?

有没有办法让 redis 返回正确的类型而不是字符串? 我这样说是因为它对于列表和日期时间甚至浮点值都是一样的。

这可能是我正在使用的 redis-py 客户端而不是 redis 本身的问题吗?

【问题讨论】:

  • 我一直在搞乱他们的发行版附带的 src/redis-cli。不幸的是,它看起来像 redis 将整数存储为字符串,并在执行 incr/decr 操作时推断出类型。
  • 你可以使用 if int(myserver.get('counter'))

标签: python types redis type-conversion


【解决方案1】:

一种解决方案是使用json.loads()intfloat 作为正确类型返回,而不是str

import json
import redis

redis = redis.from_url("redis://************")

def get(key, decode=True):
    """set decode to False when value stored as a string"""
    value = redis.get(key)
    if not decode:
        return value
    if value is not None:
        try:
            return json.loads(value)
        except json.decoder.JSONDecodeError:
            # not containing a JSON document
            return value

redis.set("foo", 2)
redis.set("bar", 2.75)

get("foo")
# 2
get("bar")
# 2.75

【讨论】:

    【解决方案2】:

    根据需要,我们有 3 种方式将数据存储到 Redis 中

    字符串

    • 所有数据都以字节形式存储和读回。
    • 这是 Redis 使用的原生方式方法。
    • 我们可以使用redis-cli 来检查和操作数据
    • 但是,数据可能无法有效存储。例如,12345 存储为至少使用 5 个字节的字符串,即使它可以用 2 字节的无符号短整数表示。
    • 您可以限制使用 Redis 提供的各种数据结构。
    • 存储在 Redis 中的数据可以跨不同版本的 Python 和编程语言移植。

    泡菜

    • 所有 Python 数据都使用pickle 库转换为二进制数据,然后再存储到 Redis 中。
    • 必须使用pickle 库将数据转换回 Python 数据类型,然后才能使用。
    • 它可以对 Redis 不提供的复杂和动态数据结构进行建模。
    • 一旦将数据转换回 Python 数据类型,我们就可以使用强大的 Python 来操作数据。
    • 它在 Redis 中使用更多内存,因为 pickle 在其输出中包含元数据和引用。
    • 不能使用redis-cli来操作数据,比如INCR
    • 存储在 Redis 中的数据不能跨不同版本的 Python 和编程语言移植。

    结构

    • 所有 Python 数据都使用 struct 库转换为二进制数据,然后再存储到 Redis 中。
    • 必须使用struct 库将数据转换回 Python 数据类型,然后才能使用。
    • 它可以对 Redis 不提供的复杂和静态数据结构进行建模。
    • 一旦将数据转换回 Python 数据类型,我们就可以使用强大的 Python 来操作数据。
    • 在这 3 种方法中,它使用的内存最少
    • 不能使用redis-cli来操作数据,比如INCR
    • 存储在 Redis 中的数据可以跨不同版本的 Python 和编程语言移植。
    from redis import Redis
    import sys
    import pickle
    import struct
    
    redis = Redis()
    
    # Data
    d = [1234, 3.1415, b'Good', True]
    k = ['int', 'float', 'byte', 'bool']
    
    ## Using String
    print(f'String\n{"_"*50}')
    # Need to convert True to string or int or float
    d2 = [1234, 3.1415, b'Good', int(True)]
    redis.hset('k0', mapping=dict(zip(k,d2)))
    print(redis.hmget('k0', *k))
    print()
    
    
    ## Using Pickle
    print(f'Pickle\n{"_"*50}')
    packed = pickle.dumps(d)
    print(f'Pickled data occupies {sys.getsizeof(packed)} bytes')
    print(packed)
    redis.set('k1', packed)
    print(f'{pickle.loads(redis.get("k1"))}')
    print()
    
    
    ## Using Struct
    print(f'Struct\n{"_"*50}')
    s = struct.Struct('H f 10s ?')
    print(f'The structure occupies {s.size} bytes.')
    packed = s.pack(*d)
    print(f'{packed}')
    redis.set('k2', s.pack(*d))
    print(f'{s.unpack(redis.get("k2"))}')
    

    预期输出:

    String
    __________________________________________________
    [b'1234', b'3.1415', b'Good', b'1']
    
    Pickle
    __________________________________________________
    Pickled data occupies 69 bytes
    b'\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00]\x94(M\xd2\x04G@\t!\xca\xc0\x83\x12oC\x04Good\x94\x88e.'
    [1234, 3.1415, b'Good', True]
    
    Struct
    __________________________________________________
    The structure occupies 19 bytes.
    b'\xd2\x04\x00\x00V\x0eI@Good\x00\x00\x00\x00\x00\x00\x01'
    (1234, 3.1414999961853027, b'Good\x00\x00\x00\x00\x00\x00', True)
    

    【讨论】:

      【解决方案3】:

      虽然利用set_response_callback 对简单的数据类型来说很好,但如果您想知道存储字典、列表、元组等内容的最快和最简单的方法 - 并保留它们可能或可能不是的 Python 原生数据类型包含——我推荐使用python内置的pickle库:

      # Imports and simplified client setup
      >>> import pickle
      >>> import redis
      >>> client = redis.Redis()
      # Store a dictionary
      >>> to_store = {'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]}
      >>> client.set('TestKey', pickle.dumps(to_store))
      True
      # Retrieve the dictionary you just stored.
      >>> retrieved = pickle.loads(client.get('TestKey'))
      {'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]}
      

      这是一个简单的客户端,它将减少上述示例中的 pickle 样板,并为您提供一个干净的接口,用于在 Redis 中存储和检索本机 python 数据类型:

      """Redis cache."""
      import pickle
      import redis
      
      redis_host = redis.Redis()
      
      
      class PythonNativeRedisClient(object):
          """A simple redis client for storing and retrieving native python datatypes."""
      
          def __init__(self, redis_host=redis_host):
              """Initialize client."""
              self.client = redis_host
      
          def set(self, key, value, **kwargs):
              """Store a value in Redis."""
              return self.client.set(key, pickle.dumps(value), **kwargs)
      
          def get(self, key):
              """Retrieve a value from Redis."""
              val = self.client.get(key)
              if val:
                  return pickle.loads(val)
              return None
      
      redis_client = PythonNativeRedisClient()
      

      用法:

      >>> from some_module import redis_client
      >>> to_store = {'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]}
      >>> redis_client.set('TestKey', to_store)
      True
      >>> retrieve = redis_client.get('TestKey')
      {'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]}
      

      【讨论】:

        【解决方案4】:

        你可以设置 decode_respone 像 True

        redis.StrictRedis(host="localhost", port=6379, db=0, decode_responses=True)
        

        【讨论】:

        • 好像不行 >>> r= redis.StrictRedis(decode_responses=True) >>> r.set('world', 123) True >>> r.get('world') '123'
        【解决方案5】:

        正如@favoretti 所说,响应回调可以解决问题。一点也不复杂,只要一条线就可以搞定。

        In [2]: import redis
        In [3]: r = redis.Redis()
        In [10]: r.set_response_callback('HGET', float)
        In [11]: r.hget('myhash', 'field0')
        Out[11]: 4.6
        

        对于hmget,它返回的是一个字符串列表,而不是单个字符串,所以你需要构造一个更全面的回调函数:

        In [12]: r.set_response_callback('HMGET', lambda l: [float(i) for i in l])
        
        In [13]: r.hmget('myhash', 'field0')
        Out[13]: [4.6]
        

        hgetall 也一样。

        【讨论】:

        • 尝试为“HMGET”和“HGETALL”设置浮动回调,但不幸的是失败了。我猜它会尝试转换哈希键名。
        【解决方案6】:

        这是我的测试。两个redis连接:一个返回int类型,另一个返回float

        import redis
        
        age_field = redis.Redis()
        age_field.set_response_callback('HGET', int)
        age_field.hget('foo', 'age')
        # OUT: 40
        a =age_field.hget('foo', 'age')
        type(a)
        # OUT: <type 'int'>
        
        gpa_field = redis.Redis()
        gpa_field.set_response_callback('HGET', float)
        gpa_field.hget('foo', 'gpa')
        # OUT: 2.5
        b = gpa_field.hget('foo', 'gpa')
        type(b)
        # OUT: <type 'float'>
        

        【讨论】:

          【解决方案7】:

          看起来redis就是这样存储数据的:

          redis 127.0.0.1:6379> set counter 5
          OK
          redis 127.0.0.1:6379> type counter
          string
          redis 127.0.0.1:6379> incr counter
          (integer) 6
          redis 127.0.0.1:6379> type counter
          string
          

          如果您真的想要,您可能会为redis-py 客户端打补丁以推断数据类型。

          【讨论】:

          • 显然我只是认为 redis-py 会有一个机制来解决这个问题,谢谢
          【解决方案8】:

          从技术上讲,您需要自己处理。

          但是,请查看this link,尤其是在他们的 README 中提到解析器和响应回调的部分,也许这是您可以使用的东西。问题是这对你来说是不是矫枉过正。

          【讨论】:

          • 谢谢我检查了解析器,但它们似乎使我想做的事情过于复杂,最后我决定使用 cpickle 来处理数据的序列化
          猜你喜欢
          • 2021-02-10
          • 1970-01-01
          • 2011-02-23
          • 2010-09-29
          • 1970-01-01
          • 2018-07-26
          • 2013-09-26
          • 2016-06-19
          • 1970-01-01
          相关资源
          最近更新 更多