Django-redis如何支持存取整型和布尔值

redis支持5种数据类型,分别是string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。整型值和布尔值并不在其中。存储整型和布尔值时,redis会将其转换为字符串

127.0.0.1:6379> SET test_key 1
OK
127.0.0.1:6379> GET test_key
"1"
127.0.0.1:6379> SET test_key True
OK
127.0.0.1:6379> GET test_key
"True"
127.0.0.1:6379> SET test_key TRUE
OK
127.0.0.1:6379> GET test_key
"TRUE"
127.0.0.1:6379> SET test_key true
OK
127.0.0.1:6379> GET test_key
"true"

但是当我们使用了django-redis之后发现,我们可以正常存取整型和布尔值,这是怎么做的呢?答案自然是在源码中,直觉告诉我们,set函数里一定做了什么事情。

# django_redis.client.default.py
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, client=None, nx=False, xx=False):
    """
    Persist a value to the cache, and set an optional expiration time.
    Also supports optional nx parameter. If set to True - will use redis setnx instead of set.
    """

    if not client:
        client = self.get_client(write=True)

    nkey = self.make_key(key, version=version)
    nvalue = self.encode(value)

    ......

set方法中调用了类的encode的方法对value进行了处理,我们再看encode

django_redis.client.default.py
def encode(self, value): """ Encode the given value. """

if isinstance(value, bool) or not isinstance(value, integer_types):
    value = self._serializer.dumps(value)
    value = self._compressor.compress(value)
    return value

return value

看到了吧,如果是布尔值,那么要调用一个dumps方法,我们找到这个dumps方法:

django_redis.serializers.pickle.py
class PickleSerializer(BaseSerializer): ......

def dumps(self, value):
    return pickle.dumps(value, self._pickle_version)

def loads(self, value):
    return pickle.loads(force_bytes(value))

原来是用了pickle来做了序列化,这样拿出来的时候调用pickle.loads方法,就可以还原为原来的对象了。这也是django-redis支持存储任意python对象的原因,pickle帮了大忙。

至于int的存取就简单了,我们从get方法看过去:

django_redis.client.default.py
def get(self, key, default=None, version=None, client=None): """ Retrieve a value from the cache. Returns decoded value if key is found, the default if not. """ if client is None: client = self.get_client(write=False)

key = self.make_key(key, version=version)

try:
    value = client.get(key)
except _main_exceptions as e:
    raise ConnectionInterrupted(connection=client, parent=e)

if value is None:
    return default

return self.decode(value)

可以看到对从redis中取出来的值调用了decode方法:

django_redis.client.default.py
def decode(self, value): 
""" 
Decode the given value. 
""" 
try: 
    value = int(value)
except (ValueError, TypeError):
    try:
        value = self._compressor.decompress(value)
    except CompressorError:
        # Handle little values, chosen to be not compressed pass
        value = self._serializer.loads(value) return value

一目了然了吧,总结的话就是:redis存的时候还是存的字符串,但是取出来的时候会尝试去int一下。

大家可能会有这样的疑问,如果值是'123'这样的字符串怎么办呢?这样并不会有问题啦,因为字符串存到数据库之前会先做pickle.dumps,这样就可以区分开数字转换成的字符串真实的字符串了。

另外,将整型进行pickle也是可以的,但是我觉得因为数字的存储是很平常的需求,而pickle之后会带上对象的各种附加信息,会增加空间消耗,所以作者才有了这样的设计。

如果大家需要写自己的redis client的话,可以参考一下这些设计,会给使用带来很大的方便。

Note: Python3picklecPickle已经合并为pickle