仕事でPythonアプリケーションからアクセスするRedisの導入を検討した際に、redis-pyでRedisを参照・更新する方法について調べましたので、備忘録にしておきます。
redis-pyのドキュメントはこちらです。
http://redis-py.readthedocs.io/
DockerでRedisコンテナを起動
実験用の環境をDockerで立ち上げ、localhost:6379で公開します。
$ docker run --name redis -d -p 6379:6379 redis redis-server --appendonly yes $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0481cc795f8f redis "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp redis
インストール
いつもどおりpipでインストールします。
$ pip install redis $ pip list | grep redis redis 2.10.6
接続
StrictRedisクラスを生成することでRedisとのコネクションを張ります。
なお、Redisクラスというのもあり、これでも接続できますが、後方互換性維持のために残されているインタフェースなので、新しく作るアプリケーションではStrictRedisを使ったほうが良さそうです。
import redis # ホスト、ポート、DB番号を指定 conn = redis.StrictRedis(host='localhost', port=6379, db=0)
ConnectionPoolクラスでコネクションプールも作れます。
こちら↓の方の投稿では、接続まで倍くらい早いようです。
PythonでRedisを扱う(redis-pyの基本) - [Dd]enzow(ill)? with DB and Python
redis_pool = redis.ConnectionPool(host='localhost', port=6379, db=0, max_connections=4) conn = redis.StrictRedis(connection_pool=redis_pool)
文字列値のset/get
まずはkey-valueのvalueが単一の文字列の場合を見ていきます。
基本は、StrictRedisクラスのset(key, value)で値の更新、get(key)で値の参照となります。
# 値のset conn.set('key01', 'value01') # 成功するとTrueが返る # 値のget value = conn.get('key01') # バイナリ値で取得 print(type(value)) # <class 'bytes'> print(str(value, encoding='utf-8')) # str()で変換 # 'value01'
存在しないキーでgetするとNoneが返されます。
print(conn.get('keyXX')) # None
また、存在しているキーでsetすると上書きされます。
conn.set('key01', 'new value') conn.get('key01') # b'new value'
なお、キーの削除はdelete()
conn.delete('key01') # 返り値は削除したキー数 conn.delete('key02', 'key03') # 複数同時削除も可能
有効期限
キーに有効期限を設定することもできます。
引数ex
に秒数を指定すると、その秒数経過後にgetしても値は取り出せなくなります。ミリ秒で指定する場合は引数px
を使います。
conn.set('key02', 'value02', ex=10) print(conn.get('key02')) # b'value02' # 10秒後に再度get print(conn.get('key02')) # None
set後に有効期限を更新する場合、expire(key, seconds)で、有効期限を秒数で設定することができます。また、expireat(key, when)ならdatetime型の値を渡すことで"いつ無効にするのか"といったこともできます。
import datetime # "key03"を10秒後に削除 conn.expire('key03', 10) # "key04"を2018/6/3 17時に削除 conn.set('key04', 'value04') conn.set('key04', datetime.datetime(2018, 6, 3, 17))
リスト
valueに使えるのは単一の値だけではありません。複数の値を関連付けられるvalueの型として、リスト、ハッシュ、セットというのが提供されています。
リスト型は、順序を持つ連結リストです。左端(先頭)または右端(末尾)から値を挿入・削除できます。
# 末尾へ"A", "B", "C"の順で追加 conn.rpush('key01', 'A') # 1 conn.rpush('key01', 'B') # 2 conn.rpush('key01', 'C') # 3 # 先頭へ"Z"を追加 conn.lpush('key01', 'Z') # 4 # 先頭から2つを参照 conn.lrange('key01', 0, 1) # [b'Z', b'A'] # 先頭から4つを参照 conn.lrange('key01', 0, 3) # [b'Z', b'A', b'B', b'C'] # 先頭から1つを取り出す (取り出された"Z"は削除されます) conn.lpop('key01') # b'Z' conn.lrange('key01', 0, 2) # [b'A', b'B', b'C'] # 末尾から1つを取り出す (取り出された"C"は削除されます) conn.rpop('key01') # b'C' conn.lrange('key01', 0, 1) # [b'A', b'B']
ハッシュ
値が(Pythonで言うところの)ディクショナリ型となっている、ハッシュ型という型もあります。
幅(width)と高さ(height)を持つ長方形を格納する場合を考えてみましょう。
# ハッシュ型の追加 conn.hset('rect1', 'width', '10.0') conn.hset('rect1', 'height', '15.0') # ハッシュ型の参照 conn.hget('rect1', 'width') # b'10.0' # キーに紐づくすべての値をdict型で返す conn.hgetall('rect1') # {b'width': b'10.0', b'height': b'15.0'} # dict型で追加 conn.hmset('rect2', {'width':'7.5', 'height':'12.5'}) conn.hgetall('rect2') # {b'width': b'7.5', b'height': b'12.5'}
セット
集合内に重複した値を持たないのがセット型です。Pythonのset型の値と対応します。
# セット型の追加 conn.sadd('key01', 'A') # セット型の参照 conn.smembers('key01') # {b'A'} conn.sadd('key01', 'B') conn.sadd('key01', 'C') conn.sadd('key01', 'A') # 重複キーでsaddした場合、返り値は0 # 重複する"A"は1つになる conn.smembers('key01') # {b'A', b'C', b'B'} conn.sadd('key02', 'B', 'D', 'B', 'A') conn.smembers('key02') # {b'D', b'A', b'B'} # 積集合 conn.sinter('key01', 'key02') # {b'A', b'B'} # 和集合 conn.sunion('key01', 'key02') # {b'D', b'A', b'C', b'B'}
ソート済みセット
上述のセットは順序を持ちませんが、ソート済みセットはある浮動小数(スコアと呼ばれる)によってソートされたセットです。
例えば、4人の生徒の数学のテストの点数をソート済みセットで表現してみます。
# ソート済みセットの追加 conn.zadd('math', 75.0, 'Tanaka') # ソート済みセットの取得 conn.zrange('math', 0, 1) # [b'Tanaka'] # スコア付き conn.zrange('math', 0, 0, withscores=True) # [(b'Tanaka', 75.0)] conn.zadd('math', 95.0, 'Suzuki', 60.0, 'Sato', 75.0, 'Komeda') conn.zcard('math') # 4 # 昇順 conn.zrange('math', 0, 3, withscores=True) # [(b'Sato', 60.0), (b'Komeda', 75.0), (b'Tanaka', 75.0), (b'Suzuki', 95.0)] # 降順 conn.zrange('math', 0, 3, desc=True, withscores=True) # [(b'Suzuki', 95.0), (b'Tanaka', 75.0), (b'Komeda', 75.0), (b'Sato', 60.0)] # 降順でトップ3つ conn.zrange('math', 0, 2, desc=True, withscores=True) # [(b'Suzuki', 95.0), (b'Tanaka', 75.0), (b'Komeda', 75.0)]
おわりに
redis-pyを使って、PythonからRedisをCRUDする方法についてまとめました。
ここで紹介した以外にも、ビット配列型やインクリメンタルに参照するスキャンなどもredis-pyで扱えますので、ドキュメントを参考にしてみてください。