[Python3 入門 19日目]8章 データの行き先（8.4〜8.5）

#8.4 NoSQLデータストア

- 非常に大きなデータセットの処理、柔軟なデータ定義、カスタムデータ処理のサポートなどを目的として作られている。

##8.4.1 dbmファミリ

- dbm形式はキーバリューストア（複数のキーと値の組）
- dbmデータストアの性質
 - キーに値を代入でき、代入された値は自動でディスク上のDBに保存される。
 - キーから値を取得できる。

```py

#"c"モードは読み書き両用
>>> db=dbm.open("definitions","c")
#辞書と同じようにキーに値を代入。
>>> db["mustard"]="yellow"
>>> db["ketchup"]="red"
>>> db["pesto"]="green"
>>> db.close
<built-in method close of _dbm.dbm object at 0x101bb0290>
>>> db.close()
#書き直したものが実際に保存されているかどうか確認。
>>> db=dbm.open("definitions","r")
#キーと値はbytesとして格納される。
>>> db["mustard"]
b'yellow'


```
##8.4.2 memcached

- キーと値のための高速なインメモリのキャッシュサーバー。
- DBの前処理として使用されたり、webサーバーのセッションデータの格納に使われたりすることが多い。
- 接続後は以下のようなことができる。
 - キーを指定した値の取得、設定
 - 値のインクリメント、デクリメント
 - キーの削除
- データは永続的ではなく、古いものから消える場合あり。**古いデータを捨ててメモリを使い切るのを防ぐ。**

##8.4.3 Redis

- データ構造サーバーで原則としてメモリに収める。
- memcachedとは以下の点が異なる。
 - ディスクにデータ保存できるため、古いデータを残しておける。
 - 単純な文字列以外のデータ構造もある。

###8.4.3.1 文字列

```py:エラー

>>> import redis
#Redisサーバーに接続する。
>>> conn=redis.Redis()
>>> conn.keys("*")
#エラー発生。
```

- 以下のようなエラーが出てかなり1時間ほど調べまくりました。⇨**Homebrewでredisのインストールを行うことでエラー対処しました。**

```py:対処方法

During handling of the above exception, another exception occurred:

$ brew search redis
==> Successfully started `redis` (label: homebrew.mxcl.redis)

```

```py:結果

>>> import redis
>>> conn = redis.Redis()
>>> conn.keys()
[]
#set()により値の書き込み。
>>> conn.set("secret","ni!")
True
>>> conn.set("caratst","24")
True
>>> conn.set("fever","101.5")
True

#キーを使って値の読み出し。
>>> conn.get("secret")
b'ni!'
>>> conn.get("caratst")
b'24'
>>> conn.get("fever")
b'101.5'

#setnx()メソッドはキーが存在しない限り値を設定する。
>>> conn.setnx("secret","icky-icky-icky-ptang-zoop-boing!")
False
>>> conn.get("secret")
b'ni!'

#getset()メソッドは元の値を返し、新しい値を設定する。
>>> conn.getset("secret","icky-icky-icky-ptang-zoop-boing!")
b'ni!'
>>> conn.get("secret")
b'icky-icky-icky-ptang-zoop-boing!'

#getrange()は部分文字列を取り出す。
>>> conn.getrange("secret",-6,-1)
b'boing!'

#setarrange()は部分文字列を置換する。
>>> conn.setrange("secret",0,"ICKY")
32
>>> conn.get("secret")
b'ICKY-icky-icky-ptang-zoop-boing!'

#mset()を使って同時に複数のキーを取得する。
>>> conn.mset({"pie":"cherry","cordial":"sherry"})
True

#mget()を使うと一度に複数の値が取得される。
>>> conn.mget(["fever","caratst"])
[b'101.5', b'24']

#キーが削除される。
>>> conn.delete("fever")
1
>>> conn.incr("caratst")
25
>>> conn.decr("caratst",10)
15
>>> conn.set("caratst","24")
True
>>> conn.incr("caratst",10)
34
>>> conn.decr("caratst")
33
>>> conn.decr("caratst",15)
18
>>> conn.set("fever","101.5")
True
>>> conn.incrbyfloat("fever")
102.5
>>> conn.incrbyfloat("fever",0.5)
103.0

#decrbyfloat()はなく、負のインクリメントをする。
>>> conn.incrbyfloat("fever",-2.0)
101.0

```

###8.4.3.2 リスト

- Redisリストは文字列しか格納できない。

```py

#先頭への挿入にはlpushを使う。
>>> conn.lpush("zoo","bear")
1
#先頭に複数の要素を挿入する。
>>> conn.lpush("zoo","alligator","duck")
3
#linsert()を使って値の前に挿入する。
>>> conn.linsert("zoo","before","bear","behavor")
4
#linsert()を使って値の後ろに挿入する。
>>> conn.linsert("zoo","after","bear","aaaar")
5
#lset()を使い、指定した位置に挿入する。
>>> conn.lset("zoo",2,"marmoset")
True

#rpush()を使って末尾に挿入する。
>>> conn.rpush("zoo","marmoset")
6
#lindex()を使ってオフセットの値を得る。
>>> conn.lindex("zoo",3)
b'bear'
#lrange()を使って指定したオフセット範囲の値を得る。
>>> conn.lrange("zoo",0,2)
[b'duck', b'alligator', b'marmoset']
#ltrim()を使ってリストを刈り込む。指定されたオフセットの範囲の要素だけ残る。
>>> conn.ltrim("zoo",1,4)
True
>>> conn.lrange("zoo",0,-1)
[b'alligator', b'marmoset', b'bear', b'aaaar']


```

###8.4.3.3 ハッシュ

- RedisのハッシュはPythonの辞書とよく似ているが文字列しか格納できない。 


```py

#hmset()を使ってsongハッシュにdo,reフィールドを同時にセットする。
>>> conn.hmset("song",{"do":"aA","re":"About a deer"})
True
#hset()を使ってハッシュ内のフィールドに1個のフィールドの値を設定する。
>>> conn.hset("song","mi","a note to follow me")
1
#hget()を使って1個のフィールドの値を取得する。
>>> conn.hget("song","mi")
b'a note to follow me'
#hmget()を使って複数のフィールドの値を取得する。
>>> conn.hmget("song","re","do")
[b'About a deer', b'aA']
#hkeys()を使ってハッシュの全てのフィールドのキーを取得
>>> conn.hkeys("song")
[b'do', b're', b'mi']
#havals()を使ってハッシュの全てのフィールドの値を取得
>>> conn.hvals("song")
[b'aA', b'About a deer', b'a note to follow me']
#ハッシュのフィールド数を取得
>>> conn.hlen("song")
3
#hegetall()を使ってハッシュ内の全てのフィールドの値とキーを取得する。
>>> conn.hgetall("song")
{b'do': b'aA', b're': b'About a deer', b'mi': b'a note to follow me'}
#hsetnx()を使ってキーがまだなければフィールドを設定する。
>>> conn.hsetnx("song","fa","asdf")
1

```

###8.4.3.4 集合

- Redisの集合はPythonの集合とよく似ている。

```py

#集合に1個または複数の値追加。
>>> conn.sadd("zoo2","a","b","c")
3
#集合の値の数を取得する。
>>> conn.scard("zoo2")
3
#集合の全ての値を取得する。
>>> conn.smembers("zoo2")
{b'a', b'c', b'b'}
#集合の値を取り除く。
>>> conn.srem("zoo2","c")
1

#集合作成
>>> conn.sadd("zoo3","ni","b","a")
3
#積集合を取得する。
>>> conn.sinter("zoo2","zoo3")
{b'a', b'b'}
#積集合を取得し、集合fowl_zooに結果を格納する。
>>> conn.sinterstore("fowl_zoo","zoo2","zoo3")
2
>>> conn.smembers("fowl_zoo")
{b'a', b'b'}

#和集合の作成
>>> conn.sunion("zoo2","zoo3")
{b'a', b'b', b'ni'}
#和集合を作成、結果を集合f_zooに格納する。
>>> conn.sunionstore("f_zoo","zoo2","zoo3")
3
>>> conn.smembers("f_zoo")
{b'a', b'b', b'ni'}

#sdiff()を使って差集合を作成。(zoo2にあってzoo3にないもの)
>>> conn.sdiff("zoo2","zoo3")
{b'c'}
#差集合を作成して、結果を集合zoo_saleに格納する。
>>> conn.sdiffstore("zoo_sale","zoo2","zoo3")
1
>>> conn.smembers("zoo_sale")
{b'c'}

```

###8.4.3.5 ソート済み集合

- Redisのデータ型でも特に応用範囲が広いのはソート済み集合あるいはzsetである。
- zsetは一意な値の集合だが、それぞれの値が**スコア**と呼ばれる浮動小数も持っている。


- 'str' object has no attribute 'items'というエラーが調べたのですが解決できませんでした。
解決のために以下のことを行いました。

 - 調べたところsetup-toolsのupgradeでエラー解消されるといくつかのサイトで見たので行った。

```py:結果

>>> import time
>>> now=time.time()
>>> now
1579936585.194324
>>> conn.zadd("logins","smeagol",now+(5*60))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File 
    return iter(x.items())
AttributeError: 'str' object has no attribute 'items'


```

```py:実践

$ pip install --upgrade 
Successfully installed setuptools-45.1.0

```

###8.4.3.6 ビット

```py

>>> days=["2013-02-24","2013-02-25","2013-02-26"]
#ユーザーIDを与える。
>>> big_spender=1089
>>> tire_kicker=40459
>>> late_joiner=550212
#その日にログインしてきたユーザーIDに対するビットをセットする。
>>> conn.setbit(days[0],big_spender,1)
0
>>> conn.setbit(days[0],tire_kicker,1)
0
>>> conn.setbit(days[1],big_spender,1)
0
>>> conn.setbit(days[2],big_spender,1)
0
>>> conn.setbit(days[2],late_joiner,1)
0
#3日間の各日の訪問者は？
>>> for day in days:
...     conn.bitcount(day)
... 
2
1
2
>>> conn.getbit(days[1],tire_kicker)
0
>>> conn.getbit(days[1],big_spender)
1

#毎日ログインしてきたユーザーは何人いる？
>>> conn.bitop("and","everyday",*days)
68777
>>> conn.bitcount("everyday")
1
#それは誰？→big_spender
>>> conn.getbit("everyday",big_spender)
1
>>> conn.getbit("everyday",tire_kicker)
0
>>> conn.bitop("or","alldays",*days)
68777
>>> conn.bitcount("alldays")
3

```

###8.4.3.7 キャッシュと有効期限

- Redisの全てのキーには寿命、有効期限がある。（デフォルトではこれらは永遠となっている。） 
- expire()関数を使えばキーをいつまで残しておくかをRedisに指示できる。(値は秒単位)
- expireat()コマンドを使えば指定したUnix時間にキーが無効になる。

```py

>>> import time
>>> key="now you see it"
>>> conn.set(key,"but not fot long")
True
>>> conn.expire(key,5)
True
>>> conn.ttl(key)
-2
>>> conn.get(key)
>>> time.sleep(6)
>>> conn.get(key)
>>> 

```

#8.5 復習課題

##8-1 test1という変数に"This is a test of the emergency text system"という文字列を代入し、text.txtというファイルにtest1の内容を書き込もう。

```py

>>> test1="""This is a test of the emergency text system"""
>>> fout=open("text.txt","wt")
>>> fout.write(test1)
43
>>> fout.close()

```

##8-2  text.txtというファイルを開き、その内容をtest2変数に読み出そう。test1とtest2は同じになっているかどうか。

```py

>>> with open("text.txt","rt") as infile:
...     test2=infile.read()
... 
>>> test1==test2
True

```

##8-3 次のテキストをbooks.csvというファイルに保存しよう。

```py

>>> a="""auther,book
... J R R Tolkien,The Hobbit
... Lynne Truss,"Eats, Shoots & Leaves"
... """

```

##8-4 csvモジュールとそのDictReaderメソッドを使ってbooks.csvの内容をbooks変数に読み込み、内容を表示しよう。

```py

>>> import csv
>>> with open("books.csv","rt") as fout:
...     books=csv.DictReader(fout)
...     for book in books:
...         print(book)
... 
OrderedDict([('auther', 'J R R Tolkien'), ('book', 'The Hobbit')])
OrderedDict([('auther', 'Lynne Truss'), ('book', 'Eats, Shoots & Leaves')])

```

##8-5 次の行を使ってbooks.csvというCSVファイルを作ろう。

```py

>>> text="""title,author,year
... The Weirdstone of Brisingamen,Alan Garner,1960
... Perdido Street Station,China Miéville,2000
... Thud!,Terry Pratchett,2005
... The Spellman Files,Lisa Lutsz,2007
... Small Gods,Terry Pratchett,1992
... """

>>> with open("books.csv","wt") as fout:
...     fout.write(text)
... 
202

```

##8-6 sqlite3モジュールを使って books.dbというSQLiteDBを作り、その中に、title, author,bookというフィールドを持つbookというテーブルを作成。


```py

>>> import sqlite3
>>> conn=sqlite3.connect("books.db")
>>> curs=conn.cursor()
>>> curs.execute("""CREATE TABLE book(title text,author text,year int)""")
<sqlite3.Cursor object at 0x1029e8960>
>>> db.commit()


```

##8-7 books.csvを読み出し、そのデータをbookテーブルに挿入しよう。

```py

>>> import csv
>>> import sqlite3
>>> ins_str ="insert into book values(?,?,?)"
>>> with open("books.csv","rt") as infile:
...     books=csv.DictReader(infile)
...     for book in books:
...         curs.execute(ins_str,(book["title"],book["author"],book["year"]))
... 
<sqlite3.Cursor object at 0x1029e8960>
<sqlite3.Cursor object at 0x1029e8960>
<sqlite3.Cursor object at 0x1029e8960>
<sqlite3.Cursor object at 0x1029e8960>
<sqlite3.Cursor object at 0x1029e8960>
>>> conn.commit()


```

##8-8 bookテーブルのtitle列を選択し、アルファベット順に表示しよう。

```py

#1回目に間違えてデータを挿入していたためやり直したところ前のデータが残っていた。なお、SQLiteではデータの削除ができず、テーブル削除なら可能ということが調べてわかった。
>>> sql="select title from book order by title asc"
>>> for row in conn.execute(sql):
...     print(row)
... 
('    ',)
('        Perdido Street Station',)
('        Small Gods',)
('        The Spellman Files',)
('        The Weirdstone of Brisingamen',)
('        Thud!Terry Pratchett',)
('Perdido Street Station',)
('Small Gods',)
('The Spellman Files',)
('The Weirdstone of Brisingamen',)
('Thud!',)


```

##8-9 bookテーブルの全ての列を選択し、出版年順に表示しよう。

```py

>>> for row in conn.execute("select * from book order by year"):
...     print(row)
... 
('        Thud!Terry Pratchett', '2005', None)
('    ', None, None)
('        The Weirdstone of Brisingamen', 'Alan Garner', 1960)
('The Weirdstone of Brisingamen', 'Alan Garner', 1960)
('        Small Gods', 'Terry Pratchett', 1992)
('Small Gods', 'Terry Pratchett', 1992)
('        Perdido Street Station', 'China Miéville', 2000)
('Perdido Street Station', 'China Miéville', 2000)
('Thud!', 'Terry Pratchett', 2005)
('        The Spellman Files', 'Lisa Lutsz', 2007)
('The Spellman Files', 'Lisa Lutsz', 2007)


```

##8-10 sqlalchemyモジュールを使って8-6で作ったsqlite3のbooks.dbのDBに接続しよう。8-8と同じようにtitle列を選択し、アルファベット順に表示しよう。


```py

>>> import sqlalchemy as sa
>>> conn=sa.create_engine("sqlite:///books.db")
>>> sql="select title from book order by title asc"
>>> rows=conn.execute(sql)
>>> for row in rows:
...     print(row)
... 
('    ',)
('        Perdido Street Station',)
('        Small Gods',)
('        The Spellman Files',)
('        The Weirdstone of Brisingamen',)
('        Thud!Terry Pratchett',)
('Perdido Street Station',)
('Small Gods',)
('The Spellman Files',)
('The Weirdstone of Brisingamen',)
('Thud!',)
>>> 


```

##8-11 RedisサーバーとPythonのredisライブラリをインストールしよう。そして、count(1),name("Fester Besteretester")フィールドを持つtestというRedisハッシュを作り、testの全てのフィールドを表示しよう。


```py
#brewの再開するのを忘れない!
$ brew services restart redis

>>> import redis
>>> conn=redis.Redis()

>>> conn.hmset("test",{"count":"1","name":"Fester Besteretester"})
True

#出力はbytes変数。
>>> conn.hgetall("test")
{b'count': b'1', b'name': b'Fester Besteretester'}

```

##8-12 testのcountフィールドをインクリメントして、結果を表示しよう。

```py

>>> conn.hincrby("test","count")
2
>>> conn.hget("test","count")
b'2'

```


#感想

学びたてより理解が進んだ。後は、ドキュメントやネットでのリサーチでさらに理解を深めたい。



######参考文献
「Bill Lubanovic著 『入門 Python3』(オライリージャパン発行)」

「redis ドキュメント」
http://redis.shibu.jp

「RedisをmacOSにインストールする。」
https://weblabo.oscasierra.net/redis-macos-install-homebrew/