今さらPython3 (62) - 第8章復習課題

第8章もこれで終わり。最後に復習課題をやってみる。

入門 Python 3

入門 Python 3

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

あるいは、withを使ってclose()なしのパターンでも。

>>> with open('test.txt', 'wt') as fout:
...     fout.write(test1)
... 
44
8.2

test.txtに何行あるか分からない体で行くと、

>>> with open('test.txt', 'rt') as fin:
...     test2 = fin.readlines()
... 
>>> test2
['This is a test of the emergency text system.']
>>> test2[0] == test1
True

だけど、1行だって分かりきっているんだから、こっちでいいんだよね。

>>> with open('test.txt', 'rt') as fin:
...     test2 = fin.readline()
... 
>>> test1 == test2
True
>>> 
8.3
>>> text = '''author, book
... J R R Tolkien, The Hobbit
... Lynne Truss, "Eats, Shoots & Leaves"
... '''
>>> with open('books.csv', 'wt') as fout:
...     fout.write(text)
... 
76

まんまだね。

8.4
>>> import csv
>>> with open('books.csv', 'rt') as fin:
...     cin = csv.DictReader(fin)
...     books = [row for row in cin]
... 
>>> books
[{'author': 'J R R Tolkien', ' book': ' The Hobbit'}, {'author': 'Lynne Truss', None: [' Shoots & Leaves"'], ' book': ' "Eats'}]
>>> 

あれ?失敗している。Eatsの直後のカンマで項目が分割されている。正しく処理できていないというのが答え?んなアホな。

ということで基本に立ち返る。本の中でDictWriter()のサンプルがあったので、それを試す。

>>> books_list = [{'author': 'J R R Tolkien', 'book': 'The Hobbit'}, {'author': 'Lynne Truss', 'book':'Eats, Shoots & Leaves'}]
>>> with open('books2.csv', 'wt') as fout:
...     cout = csv.DictWriter(fout, ['author', 'book'])
...     cout.writeheader()
...     cout.writerows(books_list)
... 
>>>

できたファイルをさっき作った回答案で処理してみる。

>>> with open('books2.csv', 'rt') as fin:
...     cin = csv.DictReader(fin)
...     books = [row for row in cin]
... 
>>> books
[{'author': 'J R R Tolkien', 'book': 'The Hobbit'}, {'author': 'Lynne Truss', 'book': 'Eats, Shoots & Leaves'}]
>>>

ちゃんと出来ている。という事は原因はCSVファイルの作り方にありということかな。

これは、ちゃんと処理できた方のCSV

author,book
J R R Tolkien,The Hobbit
Lynne Truss,"Eats, Shoots & Leaves"

これは、ダメだった方。

author, book
J R R Tolkien, The Hobbit
Lynne Truss, "Eats, Shoots & Leaves"

見た目じゃほとんど分からないと思うけど、カンマの後ろに無意識にスペースを入れていたのが原因。これによって、ダブルクオテーション(")がただの文字列として認識された模様。

8.5

さっきのトラブルシューティングで、すでにやってしまったような気がするけど、とりあえずやる。

>>> text = '''title,author,year
... The Weirdstone of Brisingamen,Alan Garner,1960
... Perdido Street Station,China Miéville,2000
... Thund!,Terry Pratchett,2005
... The Spellman Files,Lisa Lutz,2007
... Small Gods,Terry Pratchett,1992
... '''
>>> with open('books3.csv', 'wt') as fout:
...     fout.write(text)
... 
202

なんか1バイト長いけど、まいっか。

8.6

この章って、いろんなものを少しずつ触ってるんで、sqliteがすでに懐かしいw。

>>> import sqlite3
>>> conn = sqlite3.connect('books.db')
>>> curs = conn.cursor()
>>> curs.execute('''CREATE TABLE book (title VARCHAR(30), author VARCHAR(20), year INTEGER)''')
<sqlite3.Cursor object at 0x1020a7730>
>>> 

titleとauthorは適当な長さ付けちゃったけど収まるかな?

8.7
>>> ins = 'INSERT INTO book (title, author, year) VALUES(?, ?, ?)'
>>> with open('books3.csv', 'rt') as fin:
...     cin = csv.DictReader(fin)
...     books = [row for row in cin]
... 
>>> books
[{'author': 'Alan Garner', 'year': '1960', 'title': 'The Weirdstone of Brisingamen'}, {'author': 'China Miéville', 'year': '2000', 'title': 'Perdido Street Station'}, {'author': 'Terry Pratchett', 'year': '2005', 'title': 'Thund!'}, {'author': 'Lisa Lutz', 'year': '2007', 'title': 'The Spellman Files'}, {'author': 'Terry Pratchett', 'year': '1992', 'title': 'Small Gods'}]

後で使うからSQL文を先に用意して(ins)、csv.DictReader()を使って辞書のリストにしておく。

>>> for book in books:
...     curs.execute(ins, (book['title'], book['author'], book['year']))
... 
<sqlite3.Cursor object at 0x1020a7730>
<sqlite3.Cursor object at 0x1020a7730>
<sqlite3.Cursor object at 0x1020a7730>
<sqlite3.Cursor object at 0x1020a7730>
<sqlite3.Cursor object at 0x1020a7730>
>>> conn.commit()

コミットを打っておく。

8.8
>>> curs.execute('SELECT title FROM book ORDER BY title')
<sqlite3.Cursor object at 0x1020a7730>
>>> rows = curs.fetchall()
>>> print(rows)
[('Perdido Street Station',), ('Small Gods',), ('The Spellman Files',), ('The Weirdstone of Brisingamen',), ('Thund!',)]
>>> 

tupleで返ってくるところが美しくないかな。

>>> for row in rows:
...     print(row[0])
... 
Perdido Street Station
Small Gods
The Spellman Files
The Weirdstone of Brisingamen
Thund!
>>> 

ちなみに本では、英語圏らしくTheを取り除いた場合のパターンを紹介していたので、試してみる。

>>> curs.execute('SELECT title FROM book ORDER BY CASE WHEN (title like "The %") THEN SUBSTR(title, 5) ELSE title END')
<sqlite3.Cursor object at 0x1020a7730>
>>> for row in rows:
...     print(row[0])
... 
Perdido Street Station
Small Gods
The Spellman Files
The Weirdstone of Brisingamen
Thund!
>>> 

SUBSTR(X, Y, (Z))は、Xという文字列のY文字目から読み始めるという挙動をするみたいですね。後ろを切りたい場合はZを使うこともできると。

SQLite Query Language: Core Functions

データを取得して加工する時には、どこまでSQL側でやらせるか、どこからアプリ側に任せるかというは、なかなか一概に言えない部分はあるけど、方法を知っておくことは悪いことじゃない。

8.9
>>> curs.execute('SELECT * FROM book ORDER BY year DESC')
<sqlite3.Cursor object at 0x1020a7730>
>>> rows = curs.fetchall()
>>> for row in rows:
...      print(row)
... 
('The Spellman Files', 'Lisa Lutz', 2007)
('Thund!', 'Terry Pratchett', 2005)
('Perdido Street Station', 'China Miéville', 2000)
('Small Gods', 'Terry Pratchett', 1992)
('The Weirdstone of Brisingamen', 'Alan Garner', 1960)
>>> 

指示はないけど、敢えて降順にしてみた。

>>> for row in curs.execute('SELECT * FROM book ORDER BY year DESC'):
...      print(*row, sep=' ,')
... 
The Spellman Files ,Lisa Lutz ,2007
Thund! ,Terry Pratchett ,2005
Perdido Street Station ,China Miéville ,2000
Small Gods ,Terry Pratchett ,1992
The Weirdstone of Brisingamen ,Alan Garner ,1960
>>> 

これは、本で紹介してあった方法。これだとfetchall()する必要がないからシンプルになるね。

8.10

sqlalchemyの登場ですね。これも思い出しながら。

>>> import sqlalchemy as sa
>>> conn = sa.create_engine('sqlite:///books.db')
>>> rows = conn.execute('SELECT title FROM book ORDER BY title')
>>> for row in rows:
...     print(row)
... 
('Perdido Street Station',)
('Small Gods',)
('The Spellman Files',)
('The Weirdstone of Brisingamen',)
('Thund!',)
>>> 

エンジンレイヤじゃなくて、SQL表現言語とかORMなんかを使った方が練習になるかなと思ったけど、テーブル定義自体を外でやっているので、そのままでは動かないと思うので、ここは断念。

8.11

Redis登場ですね。インストールはすでにやったので、まずはRedis起動。

$ redis-server /usr/local/etc/redis.conf
12362:M 25 Dec 10:48:35.603 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 3.0.1 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 12362
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

12362:M 25 Dec 10:48:35.609 # Server started, Redis version 3.0.1
12362:M 25 Dec 10:48:35.610 * DB loaded from disk: 0.002 seconds
12362:M 25 Dec 10:48:35.610 * The server is now ready to accept connections on port 6379

今度はPython側で。

>>> import redis
>>> conn = redis.Redis()
>>> conn.hmset('test', {'count':1, 'name':'Fester BesterTester'})
True
>>> conn.hvals('test')
[b'1', b'Fester BesterTester']
>>> 

フィールドって言っているから上のでOKだと思っているけど、キーとフィールド両方なら、こっちをやれば良いのかな。

>>> conn.hgetall('test')
{b'count': b'1', b'name': b'Fester BesterTester'}
8.12
>>> conn.hincrby('test', 'count')
2

これ、本の中で紹介してないじゃん。ヘルプに書いてあったので、リンクを残しておく。

Welcome to redis-py’s documentation! — redis-py 2.4.9 documentation

(つづく)