[Python3 入門 23日目]12章 パイソニスタになろう（12.1〜12.6）

#12.1 コードのテスト

##12.1.1 pylint、pyflakes、pep8によるチェック

```py:style1

a=1
b=2
print(a)
print(b)
print(c)

```

```py:実行結果

$ pylint style1.py
************* Module style1
style1.py:1:1: C0326: Exactly one space required around assignment
a=1
 ^ (bad-whitespace)
style1.py:2:1: C0326: Exactly one space required around assignment
b=2
 ^ (bad-whitespace)
style1.py:1:0: C0114: Missing module docstring (missing-module-docstring)
style1.py:1:0: C0103: Constant name "a" doesn't conform to UPPER_CASE naming style (invalid-name)
style1.py:2:0: C0103: Constant name "b" doesn't conform to UPPER_CASE naming style (invalid-name)
#先頭がEとなっているのはエラーを示す。
style1.py:5:6: E0602: Undefined variable 'c' (undefined-variable)

-------------------------------------
Your code has been rated at -10.00/10

```
エラー解消のため、cの値を追加する。

```py:style2.py

a=1
b=2
c=3
print(a)
print(b)
print(c)

```

```py:実行結果
$ pylint style2.py
************* Module style2
style2.py:1:1: C0326: Exactly one space required around assignment
a=1
 ^ (bad-whitespace)
style2.py:2:1: C0326: Exactly one space required around assignment
b=2
 ^ (bad-whitespace)
style2.py:3:1: C0326: Exactly one space required around assignment
c=3
 ^ (bad-whitespace)
style2.py:1:0: C0114: Missing module docstring (missing-module-docstring)
style2.py:1:0: C0103: Constant name "a" doesn't conform to UPPER_CASE naming style (invalid-name)
style2.py:2:0: C0103: Constant name "b" doesn't conform to UPPER_CASE naming style (invalid-name)
style2.py:3:0: C0103: Constant name "c" doesn't conform to UPPER_CASE naming style (invalid-name)

------------------------------------
Your code has been rated at -1.67/10

```
変数名を長くしてみる。

```py:style3.py
def func():

    first=1
    second=2
    third=3
    print(first)
    print(second)
    print(third)

func()

```

```py:実行結果
$ pylist style3.py
-bash: pylist: command not found
uemuratntonoAir:bin uemura$ pylint style3.py
************* Module style3
style3.py:3:9: C0326: Exactly one space required around assignment
    first=1
         ^ (bad-whitespace)
style3.py:4:10: C0326: Exactly one space required around assignment
    second=2
          ^ (bad-whitespace)
style3.py:5:9: C0326: Exactly one space required around assignment
    third=3
         ^ (bad-whitespace)
style3.py:1:0: C0114: Missing module docstring (missing-module-docstring)
style3.py:1:0: C0116: Missing function or method docstring (missing-function-docstring)

-----------------------------------
Your code has been rated at 3.75/10

```

##12.1.2 unittestによるテスト

- コードをソース管理システムにコミットする前に、まずテストプログラムを書いて、コードが全てのテストに合格することを確認する作業はグッドプラクティス。

```py:cap.py

def just_do_it(text):
     return text.capitalize()

```

```py:test_cap.py
import unittest
import cap

class TestCap(unittest.TestCase):

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_one_word(self):
        text="duck"
        result=cap.just_do_it(text)
        self.assertEqual(result, "Duck")

    def test_multiple_words(self):
        text="a veritable flock of ducks"
        result=cap.just_do_it(text)
        self.assertEqual(result, "A Veritable Flock Of Ducks")

#assertで始まる名前を持つメソッドで結果をチェックする。

if __name__=="__main__":
    unittest.main()

```

```py:実行結果

$ python test_cap.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
$ python test_cap.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
$ python test_cap.py
..F
======================================================================
FAIL: test_words_with_apostrophes (__main__.TestCap)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_cap.py", line 25, in test_words_with_apostrophes
    self.assertEqual(result, "I'm Fresh Out Of Ideas")
AssertionError: "I'M Fresh Out Of Ideas" != "I'm Fresh Out Of Ideas"

#^は実際に文字列が異なっている箇所を示している。
- I'M Fresh Out Of Ideas
?   ^
+ I'm Fresh Out Of Ideas
?   ^


----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

```

capitalize()は先頭の単語のみタイトルケースにする。

```py:cap.py
#capitalize()をtitle()に書き換え
def just_do_it(text):
     return text.title()

```

```py:実行結果

$ python test_cap.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

```

```py:test_cap.py

import unittest
import cap

class TestCap(unittest.TestCase):

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_one_word(self):
        text="duck"
        result=cap.just_do_it(text)
        self.assertEqual(result, "Duck")

    def test_multiple_words(self):
        text="a veritable flock of ducks"
        result=cap.just_do_it(text)
        self.assertEqual(result, "A Veritable Flock Of Ducks")

#assertで始まる名前を持つメソッドで結果をチェックする。

    def test_words_with_apostrophes(self):
        text="I'm fresh out of ideas"
        result=cap.just_do_it(text)
        self.assertEqual(result, "I'm Fresh Out Of Ideas")

    def test_words_with_quotes(self):
        text="\"You're despicable,\" said Daffy Duck"
        result=cap.just_do_it(text)
        self.assertEqual(result, "\"You're Despicable,\" Said Daffy Duck")


if __name__=="__main__":
    unittest.main()

```

```py:実行結果

$ python test_cap.py
...F
======================================================================
FAIL: test_words_with_quotes (__main__.TestCap)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_cap.py", line 30, in test_words_with_quotes
    self.assertEqual(result, "\"You're Despicable,\" Said Daffy Duck")
AssertionError: '"you\'re Despicable," Said Daffy Duck' != '"You\'re Despicable," Said Daffy Duck'
- "you're Despicable," Said Daffy Duck
?  ^
+ "You're Despicable," Said Daffy Duck
?  ^


----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=1)


```

##12.1.3 noseによるテスト

- noseはサードパーティパッケージ。

```py:test_cap_nose.py

import cap
from nose.tools import eq_

    def test_one_word(self):
        text="duck"
        result=cap.just_do_it(text)
        eq_(result, "Duck")

    def test_multiple_words(self):
        text="a veritable flock of ducks"
        result=cap.just_do_it(text)
        eq_(result, "A Veritable Flock Of Ducks")

    def test_words_with_apostrophes(self):
        text="I'm fresh out of ideas"
        result=cap.just_do_it(text)
        eq_(result, "I'm Fresh Out Of Ideas")

    def test_words_with_quotes(self):
        text="\"You're despicable,\" said Daffy Duck"
        result=cap.just_do_it(text)
        eq_(result, "\"You're Despicable,\" Said Daffy Duck")


```

unittestnでテストしたときに見つけたのと同じバグ。

```py:実行結果

$ nosetests test_cap_nose.py
E
======================================================================
ERROR: Failure: IndentationError (unexpected indent (test_cap_nose.py, line 4))
----------------------------------------------------------------------
...(省略)
    def test_one_word(self):
    ^
IndentationError: unexpected indent

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

```

#12.2 Pythonコードのデバッグ


```py:test_dump.py

from dump1 import dump

@dump
def double(*args, **kwargs):
    "Double every argument"
    output_list=[2*x for x in args]
    output_dict={k:2*v for k,v in kwargs.items()}
    return output_list, output_dict

if __name__=="__main__":
    output=double(3, 5, first=100,next=98.6, last=-40)

```

```py:実行結果
$ python test_dump.py
Function name: double
Input arguments: 3 5
Input keyword arguments: dict_items([('first', 100), ('next', 98.6), ('last', -40)])
Output: ([6, 10], {'first': 200, 'next': 197.2, 'last': -80})


```

#12.3 pdbによるデバッグ 

```py:capitals.py
def process_cities(filename):
    with open(filename, "rt") as file:
        for line in file:
            line=line.strip()
            if "quit" in line.lower():
                return
            country, city=line.split(",")
            city=city.strip()
            country=country.strip()
            print(city.title(),country.title(), sep=",")

if __name__=="__main__":
    import sys
    process_cities(sys.argv[1])

```

```py:実行結果
$ python capitals.py cities1.csv
Paris,France
Caracas,Venuzuela
Vilnius,Lithunia


```

cities2.csvを実行中、15行中わずか5行出力したところで停止。

```py:cities2.csv
$ python capitals.py cities2.csv
Buenos Aires,Argentinam
La Paz,Bolivia
Brasilia,Brazil
Santiago,Chile
Bogota,Colombia


```

```py:実行結果

$ python -m pdb capitals.py cities2.csv

#cと入力すると正常終了もしくはエラーで止まる。
(Pdb) c
Buenos Aires,Argentinam
La Paz,Bolivia
Brasilia,Brazil
Santiago,Chile
Bogota,Colombia
The program finished and will be restarted
> /Users/practice/bin/capitals.py(1)<module>()
-> def process_cities(filename):

#sと入力すると行を順に実行する。
(Pdb) s
> /Users/practice/bin/capitals.py(13)<module>()
-> import sys
(Pdb) s
> /Users/practice/bin/capitals.py(14)<module>()
-> process_cities(sys.argv[1])
(Pdb) s
--Call--
> /Users/practice/bin/capitals.py(1)process_cities()
-> def process_cities(filename):
(Pdb) s
> /Users/practice/bin/capitals.py(2)process_cities()
-> with open(filename, "rt") as file:

#第6行にブレークポイントセットした。
(Pdb) b 6
Breakpoint 1 at /Users/uemura/practice/bin/capitals.py:6

#ブレークポイントに当たるか、入力行を全て読み出して終了するまでプログラム実行する。
(Pdb) c
Buenos Aires,Argentinam
La Paz,Bolivia
Brasilia,Brazil
Santiago,Chile
Bogota,Colombia
> /Users/practice/bin/capitals.py(6)process_cities()
-> return
#何を読み出したのか確認する。
(Pdb) p line
'ecuador,quito'
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/uemura/practice/bin/capitals.py:6
	breakpoint already hit 1 time
#1はコード行、現在位置(->)、ブレークポイント(B)を示す。
#オプションなしのlは前回のlの表示の末尾から表示を始めるのでオプションで指定する。
(Pdb) l 1
  1  	def process_cities(filename):
  2  	    with open(filename, "rt") as file:
  3  	        for line in file:
  4  	            line=line.strip()
  5  	            if "quit" in line.lower():
  6 B->	                return
  7  	            country, city=line.split(",")
  8  	            city=city.strip()
  9  	            country=country.strip()
 10  	            print(city.title(),country.title(), sep=",")
 11  	


```

quitテストが行全体でquitになっているものだけマッチするように書き換える。

```py:capitals2.py
def process_cities(filename):
    with open(filename, "rt") as file:
        for line in file:
            line=line.strip()
            if "quit" ==line.lower():
                return
            country, city=line.split(",")
            city=city.strip()
            country=country.strip()
            print(city.title(),country.title(), sep=",")

if __name__=="__main__":
    import sys
    process_cities(sys.argv[1])

```

```py:実行結果

$ python capitals2.py cities2.csv
Buenos Aires,Argentinam
La Paz,Bolivia
Brasilia,Brazil
Santiago,Chile
Bogota,Colombia
Quito,Ecuador
Stanley,Falkland Islands
Cayenne,French Guiana
Asuncion,Paraguay
Lima,Peru
Paramaribo,Suriname
Montevideo,Uruguay
Caracas,Venezuela

```

#12.4 エラーメッセージのロギング

- ロギングのPython標準ライブラリはloggingモジュール。
 - ログに保存したいメッセージ
 - ランク付けのための優先順位レベルとそれに対応する関数
 - モジュールとの主要な通信経路となる一つ以上のロガーオブジェクト
 - メッセージを端末、ファイル等そのほかの場所に送るハンドラ
 - 出力を作成するフォーマッタ
 - 入力に基づいて判断を下すフィルタ
 

```py
>>> import logging
>>> logging.debug("Looks like rain")
>>> logging.info("And hill")
#デフォルトの優先順位レベルはWARNIGである。
>>> logging.warn("Did I hear thunder?")
__main__:1: DeprecationWarning: The 'warn' function is deprecated, use 'warning' instead
WARNING:root:Did I hear thunder?
>>> logging.critical("Stop fencing and get inside")
CRITICAL:root:Stop fencing and get inside

#デフォルトレベルはbasicConfig()で設定できる。
#DEBUG以上の全てのレベルがログに書かれる。
>>> import logging
>>> logging.basicConfig(level="DEBUG")
>>> logger.debug("Timber")
DEBUG:bunyan:Timber
>>> logging.info("And hill")
INFO:root:And hill

```

#12.5 コードの最適化

##12.5.1 実行時間の計測

```py:time1.py
#現在の時刻から処理終了時刻をひく。
from time import time

t1=time()
num=5
num*=2
print(time()-t1)

```

```py
$ python time1.py
3.0994415283203125e-06
$ python time1.py
1.9073486328125e-06
$ python time1.py
1.6689300537109375e-06


```

```py:time2.py

from time import time, sleep

t1=time()
sleep(1.0)
print(time()-t1)

```

```py:実行結果

$ python time2.py
1.0051441192626953
uemuratntonoAir:bin uemura$ python time2.py
1.0001447200775146

```

timeitモジュールのtimeit()関数を使うと便利。

```py:timeit1.py
#time.timeit("code",number,count)の構文
#timeitの内部で実行するためcodeは""で囲む必要がある。

from timeit import timeit
print(timeit("num=5;num*=2", number=1))


```

```py:実行結果

$ python timeit1.py
1.63200000000141e-06
uemuratntonoAir:bin uemura$ python timeit1.py
1.252999999999671e-06


```

```py:time2.py
#repeat()関数のrepeat引数を使うことで実行回数が増やせる。
from timeit import repeat
print(repeat("num=5;num*=2", number=1,repeat=3))


```

```py:実行結果
$ python timeit2.py
[8.809999999977169e-07, 2.57000000000035e-07, 1.659999999993611e-07]


```

##12.5.2 アルゴリズムとデータ構造

リスト内包表記とforループのどちらが早いか。

```py:time_lists.py

from timeit import timeit

def make1():
     result=[]
     for value in range(1000):
         result.append(value)
     return result

def make2():
    result=[value for value in range(1000)]
    return result

print("make1 takes", timeit(make1, number=1000), "seconds")
print("make2 takes", timeit(make2, number=1000), "seconds")


```

リスト内包表記の方が高速である。

```py:実行結果

$ python time_lists.py
make1 takes 0.07954489899999999 seconds
make2 takes 0.035908797000000006 seconds

```

#12.6 Gitでのソース管理

```py

#新しいディレクトリ作成後、移動する。
$ mkdir newdir
$ cd newdir

#カレントディレクトリのnewdirにローカルGitリポジトリを作る。
$ git init
Initialized empty Git repository in /Users/practice/bin/newdir/.git/

#Gitリポジトリにファイルを追加する。
$ git add test.py

#Gitの状態確認。
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   test.py

#-m "my first commit"というコミットメッセージをつけてコミットする。
$ git commit -m "my first commit"
[master (root-commit) e03cd1c] my first commit
 1 file changed, 1 insertion(+)
 create mode 100644 test.py

#変更はコミット済み。
$ git status
On branch master
nothing to commit, working tree clean


```

#感想

1周目ですが約1ヶ月で完走できた。ただ、アウトプットができていないのでアウトプットをメインに次の1ヶ月はやっていく。

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