关于python中redis的简单封装
最近写爬虫用到了redis,发现一个问题:
如果我的配置文件定义好了连接redis的url(指定了db),后续引用这个连接想改db库就要多些几行代码,其他地方引用又需要多写几行,为了简化这一步流程,看了下redis源码,解决了这个问题并记录了过程。
先说下redis基本操作
import redis
#初始化一个会话
conn=redis.StrictRedis.from_url(redis://:131230@127.0.0.1:6379/1?client_name=client1)
conn=redis.Redis.from_url(redis://:131230@127.0.0.1:6379/?db=1&client_name=client1)
conn= redis.ConnectionPool(host='127.0.0.1',port=6379,password=131230,db=1,client_name=client1)
以上是一些常见的连接方式,他们是等价的,实际项目中用的最多的还是第一种
大家可能会有些疑问
疑问1:StrictRedis和Redis有什么区别?
--这是官方解释,Redis是StrictRedis子类,适用于较早版本redis
疑问2:from_url和ConnectionPool连接方式有什么区别?
--没有区别,from_url方法中有这样一段代码,其实也是调用了ConnectionPool返回一个会话池
获得了连接就可以操作数据了,操作完成调用close方法就完成了整个操作,后面这些固化的代码这里就不展示了,进入这次的主题
整个redis操作流程中,redis初始化是固定的,close回收会话是固定的,每次操作的时候都需要实现这两个流程,重复的流程需要简化
实现需求:
- 把初始化流程和回收流程简化
- 长连接和短连接随意转换,可以从每次操作完是否调用close来实现
- 初始化参数是可以干预的,比如url中写好了db=1, 引用这个url给个参数就可以改变db属性
实现代码:
class redis_conn(object):
def __init__(self,**kwargs):
self.redis_url = 'redis://:131230@127.0.0.1:6379/1?client_name=client1'
self._conn=self.url_ggg(**kwargs)
def url_ggg(self,**kwargs):
url_options=dict()
url=urlparse(self.redis_url)
for name, value in iter(parse_qs(url.query).items()):
url_options[name] = value[0]
if 'db' not in url_options and url.path:
url_options['db'] = int(url.path.replace('/', ''))
except (AttributeError, ValueError):
url_options['db'] = int(url_options.get('db',0))
#这里我设置为True, 源码默认为false
url_options['decode_responses']=True
_url=url.scheme+'://'+url.netloc
url_options.update(kwargs)
return redis.StrictRedis.from_url(_url,**url_options)
def __getattr__(self, command):
def _(*args, **kwds):
return getattr(self._conn, command)(*args,**kwds)
return _
def __enter__(self):
return self._conn
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
self._conn.close()
else:
return True
(1)先说下url_ggg方法,这个方法参考了redis源码ConnectionPool.from_url方法
可以看出,redis_url解析出来的属性优先级是最高的,也就是说如果在url中定义了定义了db=1或/1 那么在初始化的时候from_url方法入参中添加db=2是无效的
优先级是 6379/1 > 6379?db=1 > from_url (redis.url, db=1),我需要的是from_url方法中附加的属性优先级最高,所以我们把url_options 和kwargs这两个字典的位置在原基础上调换下就好
(2)关于__getattr__方法
我们在调用类不存在的属性和方法的时候就会触发这个方法,比如说我们调用redis_conn类的lpush方法,类没有lpush方法和属性,触发__getattr__,函数执行后返回_wp函数的引用,继续执行到getattr(self._conn, command), self._conn就是我们的redis初始化连接,command就是传进来的 lpush,getattr就会指向self._conn.lpush,到这大家应该就通透了
(3)关于__enter__和__exit__
相信大家应该都了解with方法,如果我们想让类支持with语法就要在类中实现__enter__和__exit__方法,我们在这两个方法中分别实现初始化池和回收会话,为什么这样做
这样做是为了让我们的类在支持短连接,每次掉用时用with as语法,就不需要再写close的代码,with退出是会调用__exit__方法,这样就可以完美回收会话
当然也可以不用__enter__和_ exit __ , 用contextmanager来装饰,这个不多讨论
使用这个redis_conn类
(1)短连接
#不写db=1的话默认为0
with redis_conn(db=1) as r:
r.set('wwwangy', 'test')
(2)长连接
#url默认的client_name为client,这里client23414比url中优先级高
r2=redis_conn(db=1,client_name=client23414')
r.set('wwwangy', 'test')
………………