[Python 实战] - No.14 Python-Redis中的编码问题

Python-Redis中的编码问题

最近在写代码的时候,因为redis的编码的问题,debug了一天,最后终于搞清楚了python-redis中遇到的那些坑。在这里记录一下:

python中如何连接redis:

在python中,我们使用python-redis库来连接redis数据库。我们采用如下方式连接redis:

import redis
pool = redis.ConnectionPool(host='localhost', port=6379)
r = redis.Redis(connection_pool=pool)

我们尝试向redis中添加两个键值对:

r.set('bob','123456')
r.set('alice','987654')
print(r.keys())

'''output
[b'alice', b'bob']
'''

我们会发现,打印出的key数组,每个字符串前面加了一个b。在python3.x中,这表示这个字符串是bytes。

如果我们希望判断'bob'是否在数据库中,并打印bob对应的值,通常我们会这样做:

if 'bob' in r.keys():
    print(r.get('bob'))
else:
    print('No such key!')
    
print(r.get('bob'))
    
'''output
No such key!
b'123456'
'''

我们会发现,明明我们已经在redis中存储了('bob','123456')键值,但是我们使用get()方法却无法获取到。这是因为:

  • 在redis中,键和值都是以bytes方式存储的,r.keys()返回键也是bytes形式的。
  • 当我们判断str类型的字符串是否在 bytes类型的列表中时,结果是False
  • 由于r.get()函数实现中会有encode()操作,所以r.get()可以接受bytes形式,也可以接受str形式

例如:

r.flushall()
r.set('bob','123456')
r.set('alice','987654')

for k in r.keys():     # r.get() 可以接受bytes()类型的输入
    print(k,r.get(k))
    
if 'bob'.encode('utf-8') in r.keys():
    print(r.get('bob'))
else:
    print('No such key!')
    
k = 'bob'
print("Encode:",k,r.get(k).decode('utf-8')) # r.get()函数也可以直接传入str

'''output
b'alice' b'987654'
b'bob' b'123456'
b'123456'
Encode: bob 123456
'''

总结:

  • 在redis中存储的键值对均为bytes类型
  • 如果我们希望查询某一个str类型的键是否在数据库中,需要使用encode(),将str转换为bytes,看是否在r.keys()中;使用decode将查询出的bytes值转换为str

如果我们总是使用encode和decode来编码-解码键值对,会非常的麻烦。在python中,我们可以通过声明redis连接池的decode_responses字段来对键值对进行默认编码:

import redis
pool = redis.ConnectionPool(host='localhost', port=6379,decode_responses=True)
r = redis.Redis(connection_pool=pool)
r.flushall()

r.set('Monday','Sunny')
r.set('Tuesday','Rainy')

for k in r.keys():
    print(k,r.get(k))
    
'''output
Tuesday Rainy
Monday Sunny
'''

我们发现,当我们声明了decode_responses=True 之后,我们从redis中使用get()和keys()获得的键值对就都是str类型的了。

那么我们是不是一直使用decode_responses=True 就好了呢?为什么redis要将decode_responses默认设置成False呢?。我们分别在两种情况下进行如下操作:

  • 创建一个dict
  • 使用pickle对dict打包存储到redis中
  • 读取dict,并使用pickle加载

不使用decode_responses

import redis
import pickle
pool = redis.ConnectionPool(host='localhost', port=6379)
r = redis.Redis(connection_pool=pool)
r.flushall() # 清空redis

d = {'bob':'123456789','alice':'987654321'}
r.set('my_dict',pickle.dumps(d))
new_d = pickle.loads(r.get('my_dict'))
print(new_d)

'''output
{'bob': '123456789', 'alice': '987654321'}
'''

使用decode_responses

import redis
import pickle
pool = redis.ConnectionPool(host='localhost', port=6379,decode_responses=True)
r = redis.Redis(connection_pool=pool)
r.flushall() # 清空redis

d = {'bob':'123456789','alice':'987654321'}
r.set('my_dict',pickle.dumps(d))
new_d = pickle.loads(r.get('my_dict'))
print(new_d)

'''output
Traceback (most recent call last):
  File "<ipython-input-31-2fd8f3eab4ba>", line 9, in <module>
    new_d = pickle.loads(r.get('my_dict'))
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\client.py", line 1264, in get
    return self.execute_command('GET', name)
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\client.py", line 775, in execute_command
    return self.parse_response(connection, command_name, **options)
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\client.py", line 789, in parse_response
    response = connection.read_response()
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\connection.py", line 637, in read_response
    response = self._parser.read_response()
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\connection.py", line 332, in read_response
    response = self.encoder.decode(response)
  File "C:\ProgramData\Anaconda3\lib\site-packages\redis\connection.py", line 133, in decode
    value = value.decode(self.encoding, self.encoding_errors)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
'''

这是因为,我们使用pickle存储的时候,pickle会将对象dumps成bytes类型的字符串,并且该字符串无法使用utf-8进行decode。由于我们使用了decode_responses, 执行r.get(‘my_dict’)的时候会尝试将pickle dumps的字符串deocde为utf8,所以会抛出异常。

总结:究竟我们什么时候需要使用decode_responses呢?

  • 如果我们redis只是为了存储一些str字符串等键值对,推荐使用decode_responses来避免在代码手动decode
  • 如果在redis使用中,会涉及对象,字典,列表等的存储,不使用decode_responses
发布了118 篇原创文章 · 获赞 140 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/tjuyanming/article/details/90203494