Django-redis 实现Redis结果自动解码的一种方式

Django-redis使用时遇到的坑

Django-redis是一个为Django提供redis操作一个python库,它可以提供redis的cache和原生的redis接口,在Django使用redis时,加入这个库会非常方便,不过今天在用这个库的时候出现了一些问题,这些问题都比较麻烦,通过分析源码之后得到以下一些结论。

参数不起作用

Django-redis可以选择redis-py作为redis接口的实现类,在使用的时候碰到这样一个问题:一般我们都是用redis来缓存一些东西,而redis自身支持的数据类型有限,一般都是字符串,而这些字符串的编码可能并不确定,实际在Redis中一般是保存一个bytes的数组,而在python中字符一直都是UTF-8的数据,这二者了不一致就造成的一些问题。在一般的redis
client中为了避免类似的问题,Redis的类中支持一个decode_responses的参数,这个参数可以实现对redis返回结果的一个解码处理,在快速开发中显得十分重要,而在django-redis这个扩展中,没有实现自动解码的功能,这个主要是因为django-redis采用的是redis connection pool的原因,通过翻阅源代码发现,py-redis只在一般的client中有自动解码的选项,而如果client采用connection
pool的方式实现的话,这些多余的选项是不被支持的,究其原因是因为connection pool中的各种连接之间是有差异的,编码上可能也会有一定的差异。

曲线解决方法

既然自动解码的方式在django-redispy-redis上行不通,那么我们可以通过一种变通的方式实现一个自定义的带有解码的Redis Client, 实现的基本思路其实比较简单,实际就是在调用redis相关命令后对结果进行一个解码的处理就可以了,python中灵活的魔法方法可以非常方便的实现这个需求,在django-redis中,一个原生的redis是一个类的实例,可以通过以下的方式实现这个类的实例化:

from django_redis import get_redis_connection
conn = get_redis_connection('default')

我们需要做的是定义一个类来取代这个类,而底层的调用还是会去调用这个类的方法,不过是将其输出做一个简单的解码处理就可以了,其实现方式如下:

from django_redis import get_redis_connection

def parse_redis(obj):
    if isinstance(obj, bytes):
        return obj.decode('utf-8')
    elif isinstance(obj, set):
        return set(x.decode('utf-8') for x in obj)
    elif isinstance(obj, list):
        return [x.decode('utf-8') for x in obj]
    else:
        return obj

def tocontainer(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return parse_redis(result)
    return wrapper

class Redis(object):
    def __init__(self, conf='default'):
        self.conn = get_redis_connection(conf)

    def __getattr__(self, func):
        return tocontainer(getattr(self.conn, func))


redis = Redis('default')

在上述代码中parse_redis是一个对redis返回结果做解码处理的一个函数,tocontainer是一个函数方法,它相当于一个装饰函数,调用parse_redis对结果进行处理。Redis是取代原生``redis
client的一个类,它之所以可以取代原生的方法,主要利益于一个getattr的魔法函数,这个函数是读取类成员变量的时候会自动调用,而类的方法实际也是类下面的一种特殊的函数变量,所以可以在getattr中捕获对类方法的调用,然后将类方法的调用改成对底层conn下面方法的调用,对conn这个实例,我们可以通过getattr``方法实现对其成员变量的查找,找到相应的方法之后,对找到的方法进行包装,成为一个新的方法返回,这样就可以实现对结果的处理,就相当于用一个在原有方法基础上实现一个新的方法,并直接取代原生的方法。

发表新评论