- 浏览: 15060 次
- 性别:
- 来自: 广州
最新评论
今天用python写robot framework扩展库时候才发现,原来安装的python redis模块存在一个小小的问题
首先说一下,我的redis模块是怎么安装的:
通过 pip install redis 安装依赖库
python 脚本:
import redis
rds = redis.Redis('127.0.0.1',22555)
rds.set(None,'Test')
rds.set('None','tet')
rds.get(None)
rds.get('None')
结果你会发现一个奇怪的现象是:
rds.get('None') 返回 tet ; rds.get(None) 也是返回tet 也就是说,key为None和'None'是 没区别的。
但按道理来说不应该这样,我们知道None 类型是NoneType, 'None'类型是str (类型 具体可以通过print type(None) 和print type('None')查看)
因此这应该是两个key 所以很明显该模块有个小小问题:
我们怎么区分开这两个key呢?
首先看看python redis模块是怎么实现的。首先我们找出我们安装的redis python 模块,我的是在$HOME/app/python-2.7.3/lib/python-2.7/site-packages/redis 这个目录。
这个目录有好几个文件 client.py 、 connection.py、 exceptions.py 等好几个文件 我们只关注client.py 和 connection.py 这两个文件
client 这个文件是我们用到的客户端主要实现的文件,而connection 是把已经封装好的redis 命令发送到服务器,并把结果集返回。
client 里面有好几个类,我们常用的redis命令封装在 StrictRedis 和 Redis 这两个类中,Redis 又是StrictRedis 的子类。因此我们应该把关注点重点放在StrictRedis 和 Redis 两个类上。我们要一步步追踪,究竟是如何把命令封装成redis 命令的。
我们看一个很简单的命令--set。set 命令封装在 client 模块的 StrictRedis类中。看源码:
def set(self, name, value, ex=None, px=None, nx=False, xx=False):
pieces = [name, value]
if ex:
pieces.append('EX')
if isinstance(ex, datetime.timedelta):
ex = ex.seconds + ex.days * 24 * 3600
pieces.append(ex)
if px:
pieces.append('PX')
if isinstance(px, datetime.timedelta):
ms = int(px.microseconds / 1000)
px = (px.seconds + px.days * 24 * 3600) * 1000 + ms
pieces.append(px)
if nx:
pieces.append('NX')
if xx:
pieces.append('XX')
return self.execute_command('SET', *pieces)
__setitem__ = set
从这里面还看不出什么 ,我们注意到这一方法重点是处理后面的默认参数再前面调用究竟有没有传递,其实可以忽略,这不是我们关注点。我们注意到这个方法最后是交给execute_command去处理的,因此我们找出 execute_command的源码:
def execute_command(self, *args, **options):
"Execute a command and return a parsed response"
pool = self.connection_pool
command_name = args[0]
connection = pool.get_connection(command_name, **options)
try:
connection.send_command(*args)
return self.parse_response(connection, command_name, **options)
except ConnectionError:
connection.disconnect()
connection.send_command(*args)
return self.parse_response(connection, command_name, **options)
finally:
pool.release(connection)
重点在try 里面 connection.send_command(*args)。还是没找到我们想要的,继续找到send_command 的源代码:
def send_command(self, *args):
"Pack and send a command to the Redis server"
self.send_packed_command(self.pack_command(*args))
不多说 继续找 pack_command 源代码(在 connection 模块):
def pack_command(self, *args):
"Pack a series of arguments into a value Redis command"
output = BytesIO()
output.write(SYM_STAR)
output.write(b(str(len(args))))
output.write(SYM_CRLF)
for enc_value in imap(self.encode, args):
output.write(SYM_DOLLAR)
output.write(b(str(len(enc_value))))
output.write(SYM_CRLF)
output.write(enc_value)
output.write(SYM_CRLF)
return output.getvalue()
感觉是不是迷糊了? 哈哈
其实 就是这里我们找对了!!!!!
好吧,看这段代码之前,我们首先了解一下 redis协议的基本知识。
See the following example:
*3
$3
SET
$5
mykey
$7
myvalue
This is how the above command looks as a quoted string, so that it is possible to see the exact value of every byte in the query, including newlines.
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
看上面 *3 表示 这个redis 命令有三个字段 command_name key val(set key1 4),$3 z指的是command_name的长度,strlen('SET') = 3 ,$5表示key 长度 strlen('mykey') = 5, 同理 $7 表示 strlen('myvalue') = 7
再回头看看我们pack_command 的源代码是不是一目了然了?
output.write(b(str(len(args))))
我们封装的redis指令是通过参数传递进来的 *args ,例如:print args[0] # SET , print args[1] # mykey, print args[2] # myvalue
对比我们上面介绍的redis 协议知道吧, str(len(args)) 就是上面的*3 ;
for enc_value in imap(self.encode, args):
这里就是对args里面的参数作一定转换,同样我们找到self.encode源代码:
def encode(self, value):
"Return a bytestring representation of the value"
if isinstance(value, bytes):
return value
if isinstance(value, float):
value = repr(value)
if not isinstance(value, basestring):
value = str(value)
if isinstance(value, unicode):
value = value.encode(self.encoding, self.encoding_errors)
return value
注意到没有,if not isinstance(value, basestring):
value = str(value)
这里意思就是说,如果不是str类型,会把该值强制转换为str类型。原来这样,怪不得我们说set(None,'Test') 和set('None','Test') 是一回事,其实,不应该这样。
既然这样我们稍作修改:
if value == None:
value = value
else:
value = str(value)
这样修改就可以了吗?。。。当然不是啦!!。。。我们接下来看pack_command的代码,
for enc_value in imap(self.encode, args):
output.write(SYM_DOLLAR)
output.write(b(str(len(enc_value))))
output.write(SYM_CRLF)
output.write(enc_value)
output.write(SYM_CRLF)
return output.getvalue()
我们上面的value不再强制转换为str类型后,那么output.write(b(str(len(enc_value)))) 就有可能报异常,当enc_value 为None时len 函数报异常,因此我们要避免这种异常,可以这样改:
if enc_value == None:
output.write(b(str(len(enc_value))))
else:
output.write(b(str(0)))
同样下面的语句我们也稍微修改一下:
output.write(enc_value)
修改为:
if enc_value != None:
output.write(enc_value)
好 大工告成!!!!!!
我们测试一下 是不是这样呢? 同样使用之前的脚本:
import redis
rds = redis.Redis('127.0.0.1',22555)
rds.set(None,'Test')
rds.set('None','tet')
rds.get(None)
rds.get('None')
结果返回 rds.get(None) # "Test" rds.get('None') # "tet"
如果,你自己开发一个能兼容redis协议的并且还有属于自己扩展命令的存储引擎,我们其实也是可以通过修改redis 模块使之成为我们的客户端的,最方便的是继承Redis 类,当然这是后话。
首先说一下,我的redis模块是怎么安装的:
通过 pip install redis 安装依赖库
python 脚本:
import redis
rds = redis.Redis('127.0.0.1',22555)
rds.set(None,'Test')
rds.set('None','tet')
rds.get(None)
rds.get('None')
结果你会发现一个奇怪的现象是:
rds.get('None') 返回 tet ; rds.get(None) 也是返回tet 也就是说,key为None和'None'是 没区别的。
但按道理来说不应该这样,我们知道None 类型是NoneType, 'None'类型是str (类型 具体可以通过print type(None) 和print type('None')查看)
因此这应该是两个key 所以很明显该模块有个小小问题:
我们怎么区分开这两个key呢?
首先看看python redis模块是怎么实现的。首先我们找出我们安装的redis python 模块,我的是在$HOME/app/python-2.7.3/lib/python-2.7/site-packages/redis 这个目录。
这个目录有好几个文件 client.py 、 connection.py、 exceptions.py 等好几个文件 我们只关注client.py 和 connection.py 这两个文件
client 这个文件是我们用到的客户端主要实现的文件,而connection 是把已经封装好的redis 命令发送到服务器,并把结果集返回。
client 里面有好几个类,我们常用的redis命令封装在 StrictRedis 和 Redis 这两个类中,Redis 又是StrictRedis 的子类。因此我们应该把关注点重点放在StrictRedis 和 Redis 两个类上。我们要一步步追踪,究竟是如何把命令封装成redis 命令的。
我们看一个很简单的命令--set。set 命令封装在 client 模块的 StrictRedis类中。看源码:
def set(self, name, value, ex=None, px=None, nx=False, xx=False):
pieces = [name, value]
if ex:
pieces.append('EX')
if isinstance(ex, datetime.timedelta):
ex = ex.seconds + ex.days * 24 * 3600
pieces.append(ex)
if px:
pieces.append('PX')
if isinstance(px, datetime.timedelta):
ms = int(px.microseconds / 1000)
px = (px.seconds + px.days * 24 * 3600) * 1000 + ms
pieces.append(px)
if nx:
pieces.append('NX')
if xx:
pieces.append('XX')
return self.execute_command('SET', *pieces)
__setitem__ = set
从这里面还看不出什么 ,我们注意到这一方法重点是处理后面的默认参数再前面调用究竟有没有传递,其实可以忽略,这不是我们关注点。我们注意到这个方法最后是交给execute_command去处理的,因此我们找出 execute_command的源码:
def execute_command(self, *args, **options):
"Execute a command and return a parsed response"
pool = self.connection_pool
command_name = args[0]
connection = pool.get_connection(command_name, **options)
try:
connection.send_command(*args)
return self.parse_response(connection, command_name, **options)
except ConnectionError:
connection.disconnect()
connection.send_command(*args)
return self.parse_response(connection, command_name, **options)
finally:
pool.release(connection)
重点在try 里面 connection.send_command(*args)。还是没找到我们想要的,继续找到send_command 的源代码:
def send_command(self, *args):
"Pack and send a command to the Redis server"
self.send_packed_command(self.pack_command(*args))
不多说 继续找 pack_command 源代码(在 connection 模块):
def pack_command(self, *args):
"Pack a series of arguments into a value Redis command"
output = BytesIO()
output.write(SYM_STAR)
output.write(b(str(len(args))))
output.write(SYM_CRLF)
for enc_value in imap(self.encode, args):
output.write(SYM_DOLLAR)
output.write(b(str(len(enc_value))))
output.write(SYM_CRLF)
output.write(enc_value)
output.write(SYM_CRLF)
return output.getvalue()
感觉是不是迷糊了? 哈哈
其实 就是这里我们找对了!!!!!
好吧,看这段代码之前,我们首先了解一下 redis协议的基本知识。
See the following example:
*3
$3
SET
$5
mykey
$7
myvalue
This is how the above command looks as a quoted string, so that it is possible to see the exact value of every byte in the query, including newlines.
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
看上面 *3 表示 这个redis 命令有三个字段 command_name key val(set key1 4),$3 z指的是command_name的长度,strlen('SET') = 3 ,$5表示key 长度 strlen('mykey') = 5, 同理 $7 表示 strlen('myvalue') = 7
再回头看看我们pack_command 的源代码是不是一目了然了?
output.write(b(str(len(args))))
我们封装的redis指令是通过参数传递进来的 *args ,例如:print args[0] # SET , print args[1] # mykey, print args[2] # myvalue
对比我们上面介绍的redis 协议知道吧, str(len(args)) 就是上面的*3 ;
for enc_value in imap(self.encode, args):
这里就是对args里面的参数作一定转换,同样我们找到self.encode源代码:
def encode(self, value):
"Return a bytestring representation of the value"
if isinstance(value, bytes):
return value
if isinstance(value, float):
value = repr(value)
if not isinstance(value, basestring):
value = str(value)
if isinstance(value, unicode):
value = value.encode(self.encoding, self.encoding_errors)
return value
注意到没有,if not isinstance(value, basestring):
value = str(value)
这里意思就是说,如果不是str类型,会把该值强制转换为str类型。原来这样,怪不得我们说set(None,'Test') 和set('None','Test') 是一回事,其实,不应该这样。
既然这样我们稍作修改:
if value == None:
value = value
else:
value = str(value)
这样修改就可以了吗?。。。当然不是啦!!。。。我们接下来看pack_command的代码,
for enc_value in imap(self.encode, args):
output.write(SYM_DOLLAR)
output.write(b(str(len(enc_value))))
output.write(SYM_CRLF)
output.write(enc_value)
output.write(SYM_CRLF)
return output.getvalue()
我们上面的value不再强制转换为str类型后,那么output.write(b(str(len(enc_value)))) 就有可能报异常,当enc_value 为None时len 函数报异常,因此我们要避免这种异常,可以这样改:
if enc_value == None:
output.write(b(str(len(enc_value))))
else:
output.write(b(str(0)))
同样下面的语句我们也稍微修改一下:
output.write(enc_value)
修改为:
if enc_value != None:
output.write(enc_value)
好 大工告成!!!!!!
我们测试一下 是不是这样呢? 同样使用之前的脚本:
import redis
rds = redis.Redis('127.0.0.1',22555)
rds.set(None,'Test')
rds.set('None','tet')
rds.get(None)
rds.get('None')
结果返回 rds.get(None) # "Test" rds.get('None') # "tet"
如果,你自己开发一个能兼容redis协议的并且还有属于自己扩展命令的存储引擎,我们其实也是可以通过修改redis 模块使之成为我们的客户端的,最方便的是继承Redis 类,当然这是后话。
发表评论
-
python Mysql依赖安装解决方案
2015-03-03 14:27 578非root权限下安装python mysql依赖确实有点蛋疼, ... -
python 网络编程
2014-01-12 22:51 697python 网络编程,首先我们认识一下socket,什么是s ... -
求一个范围内的素数
2013-12-18 01:23 1581学算法时候,求素数总是一道逃不掉的练习题。 好久没写算法相关 ... -
python-调用C动态库
2013-11-19 11:51 609哥虽然作为屌丝程序员,但是也算是一个优雅的程序员,平时常用都是 ... -
python-异常处理
2013-11-14 00:04 607有一种说法: 程序错误可分为,编译时错误和运行时错误。写过c程 ... -
python 多线程
2013-11-13 15:05 640在谈多线程之前我们先 ... -
python-mysql数据库操作
2013-11-09 11:01 9821. 首先要安装MySQLdb依赖库 2. 示范mysql数据 ... -
python-类操作
2013-11-09 11:02 500格式: class 类名: 成员变量 ... -
python-字符串操作
2013-11-09 11:02 525字符串分割: s.split([s ... -
python-文件操作
2013-11-09 11:01 447文件操作: 1. 创建空文件 os.mknod('test.t ... -
python-字典
2013-11-08 21:40 419字典是python内建的数据类型 dic = { &quo ... -
python-链表
2013-11-08 21:37 615python 链表操作 list=[1, 2, 3, 45 ... -
python-函数
2013-11-08 21:35 343定义: def 函数名(参数列表): 函数 ... -
python入门--else-for-while语句篇
2013-11-08 21:32 606python if 语句 格式: (必须注意 if 、elif ...
相关推荐
redislite 是一个 Redis 的 Python 模块,用于在 Python 代码中对 Redis 数据进行操作。示例代码:>>> from redislite import Redis >>> redis_connection = Redis('/tmp/redis.db') >>> redis_connection...
安装完成后,可以在Python脚本中导入`redis`模块: ```python import redis ``` 导入后,你可以创建一个`redis`客户端对象,连接到本地运行的Redis服务器: ```python r = redis.Redis(host='localhost', port=...
安装完成后,我们可以导入`redis`模块来创建一个Redis连接。下面是一个基本的连接示例: ```python import redis # 创建连接 r = redis.Redis(host='localhost', port=6379, db=0) ``` 这里,`host`参数是Redis...
Python在大数据处理和分布式系统中经常与键值存储系统如Redis和文档型数据库MongoDB结合使用,以实现高效的数据交互。本文将详细讲解如何使用Python操作Redis和MongoDB。 首先,Redis是一个内存中的数据结构存储...
由于Python的标准库中并不包含直接操作Redis的模块,因此我们需要安装额外的第三方库来实现这一功能。这个场景下,我们讨论的是如何在Python中导入并使用`redis`库,这通常通过`pip`来安装。首先,让我们来了解一下...
Python实现与Redis交互操作主要涉及使用Python的Redis模块来与Redis数据库进行通信。Redis是一个开源的内存数据结构存储系统,可以作为数据库、缓存和消息代理。以下将详细介绍Python Redis模块的安装、导入、连接...
在"python-redis.rar"这个压缩包中,可能包含了关于如何使用Python的redis模块来实现Redis的基本操作以及高级功能如列表、队列和分布式锁的示例代码和文档。 1. Redis基本操作:Python的redis库提供了丰富的API,...
- 使用`redis`库,首先需要导入`redis`模块。 - 通过`redis.Redis()`创建连接,指定服务器的主机名、端口、数据库编号以及密码(如果有的话)。 - 示例:`r = redis.Redis(host='192.168.2.22', port=6379, db=2,...
Python Django Web典型模块开发实战_Redis缓存-解决亿万级别的订单涌进_编程项目案例实例详解课程教程.pdf
首先,我们需要导入必要的库,包括`redis`模块,它提供了Python与Redis服务器交互的接口。在代码中,我们看到`constant`模块导入了Redis的相关配置,如IP地址、数据库编号和密码。这里使用`ConnectionPool`创建一个...
`redis-py`还提供了对Redis模块(如HyperLogLog、Geo、BitField等)的支持。例如,使用HyperLogLog估算不同集合的交集大小: ```python r.pfadd('hyperloglog1', 'item1', 'item2') r.pfmerge('merged_hll', '...
Python提供了内置的`json`模块来进行JSON序列化,`json.dumps()`方法可以将字典转换成JSON格式的字符串: ```python data_dict = {'project': 'india', 'total_size': '15.8 MB'} serialized_data = json.dumps...
redis-py-cluster 该客户端提供了Redis 3.0中添加的Redis集群客户端... redis-py-cluster 2.1.x将是支持Python 2.7的最后一个主要版本。 2.1.x系列将继续获得支持Python 2的错误修复和安全补丁,直到2020年8月1日。red
本篇文章将重点讨论如何使用Python的Flask框架和Redis数据库来实现一个分布式任务分发系统。这是一套常见的解决方案,尤其适用于需要处理大量并发任务的情况,如爬虫项目。 **Flask框架** Flask是Python的一个轻量...
1. **Python Redis 库**:Python 有一个官方支持的 Redis 客户端库(`redis-py`),它提供了丰富的 API 用于操作 Redis。项目中可能包含了如何安装、导入这个库以及如何使用它的各种方法,如 `set`, `get`, `hset`, ...
redis-py模块提供了一些主要的类和方法,可以方便地操作Redis数据库。以下是一些常用的类和方法: 1. Redis类:表示与Redis数据库的连接。可以通过Redis类的实例来执行各种操作,如设置键值对、获取键值对、删除键值...
之后,还需要下载并解压我们提到的压缩包,导入对应的Redis模块。 2. **初始化客户端**:使用SDK前,需要初始化一个客户端对象,配置好腾讯云账号的密钥信息: ```python from tencentcloud.common import ...
Python操作Redis数据库是常见的数据存储和缓存策略,尤其在处理高并发和大数据量的场景中。本文将详细介绍三种在Python中与Redis交互的方法:直接使用、使用连接池以及通过Django缓存系统。 首先,要进行Python操作...
用python操作redis数据库,先下载redis-py模块下载地址https://github.com/andymccurdy/redis-py shell# wget https://github.com/andymccurdy/redis-py 然后解压 在解压目录运行 python setup.py install安装模块...