ざっくりPythonのおさらい その1。基本文法など


1年ぶりぐらいにPythonを業務で使うことになったので復習してます。

[ざっくりPythonのおさらい その1。基本文法など]
https://qiita.com/48hands/items/31b7ac2b49addbb8658c

[ざっくりPythonのおさらい その2。おもにデータ保存]
https://qiita.com/48hands/items/ab86b7463268e669d216

[ざっくりPythonのおさらい その3。Numpy, Pandas]
https://qiita.com/48hands/items/b1a71000533e129b27f2

復習に使った教材は、酒井潤さんがUdemyで提供しているこちらの講座です。
Pythonに入門するときに一度通しで勉強したのですが、入門書として私の中ではベストでした。

[講座名]
Python3入門 + 応用 + アメリカのシリコンバレー流コードスタイルを学び、実践的なアプリ開発の準備をする
https://www.udemy.com/python-beginner/

入門〜応用までかなり幅広く網羅しているので、市販で売ってる入門書籍よりも個人的にかなりおすすめです。
一方で他言語を学んでいない初心者には中半〜後半は難しいかもしれません。
説明は非常にわかりやすかったです。

Udemyの割引対象にもなっていた(1200円)ので本を何冊も買うより、こっちの方が実践的な内容を広く学べて割安です。

## 起動引数を扱う

### sysを使った簡単な実装

```python
import sys

print(sys.argv)

for arg in sys.argv:
    print(arg)
```

```
$ python index.py ppppp eeeee
['index.py', 'ppppp', 'eeeee']
index.py
ppppp
eeeee
```

### optparserを使った実装

```python
from optparse import OptionParser


def main_process():
    # usageで使い方の説明
    # usageをパーサーに渡すことによって、--helpで表示できる。
    usage = 'usage: %prog [options] arg1 arg2'

    # パーサーの生成
    parser = OptionParser(usage=usage)

    # -f,もしくは--fileで指定するように指定する
    # actionは渡されたものをストアすることを示している
    # typeにstringを指定して文字列を受け取るようにしている
    # destで代入先の変数名をfilenameとしている。
    parser.add_option('-f', '--file', action='store', type='string',
                      dest='filename', help='ファイル名を指定してください。')

    # -n,もしくは--numで指定するように指定する
    # actionは渡されたものをストアすることを示している
    # typeにintを指定して整数を受け取れるようにしている
    # destで代入先の変数名をnumとしている。
    parser.add_option('-n', '--num', action='store', type='int', dest='num')

    # -vを指定すると、verboseにTrueが代入されるようにしている。
    # デフォルト値にFalseを指定している。
    parser.add_option('-v', action='store_true', dest='verbose', default=False)

    # -vを指定すると、verboseにFalseが代入されるようにしている。
    # parser.add_option('-v', action='store_false', dest='verbose')

    # optionsとargsに分けてアンパッキング
    options, args = parser.parse_args()
    print(options)
    print(args)

    # 変数を取得
    print(options.filename)
    print(options.num)
    print(options.verbose)


if __name__ == '__main__':
    main_process()
```

```
$ python main_app.py --help

Usage: main_app.py [options] arg1 arg2

Options:
  -h, --help            show this help message and exit
  -f FILENAME, --file=FILENAME
                        ファイル名を指定してください。
  -n NUM, --num=NUM     
  -v     

$ python main_app.py -f test.txt  a b c -n 123456

{'filename': 'test.txt', 'num': 123456, 'verbose': False}
['a', 'b', 'c']
test.txt
123456
False
```


## for文関連のおさらい

### for-else文

```python
# breakがない場合はelseが実行される
for fruit in ['apple', 'pen']:
    print(fruit)
else:
    print('I ate all')

# breakがある場合はelseは実行されない
for fruit in ['apple', 'pen']:
    print(fruit)
    break
else:
    print('I ate all')
```

### range関数

```python
# 0から9まで
for i in range(10):
    print(i)

# 2から9まで
for i in range(2,10):
    print(i)

# 2から始まって9まで3スキップ
for i in range(2, 10, 3):
    print(i)

# index番号を使わないときは変数名を`_`にして使わない意思を示す。
for _ in range(10):
    print('hello')
```

### enumerate関数

```python
# index付きで表示したいときに利用する
for i, fruit in enumerate(['apple', 'banaana', 'orange']):
    print(i, fruit)
```

### zip関数

```python
days = ['Mon', 'Tue', 'Wed']
fruits = ['apple', 'banana', 'orange']
drinks = ['coffee', 'tea', 'beer']

# zip関数を使わない場合
for i in range(len(days)):
    print(days[i], fruits[i], drinks[i])

# zip関数を使った場合
for day, fruit, drink in zip(days, fruits, drinks):
    print(day, fruit, drink)
```

### 辞書のfor文処理

```python
d = {'x': 100, 'y': 200}
for k, v in d.items():
    print(k, v)
```

`d.items()`は`dict_items([('x', 100), ('y', 200)])`を返している。
アンパッキングで`k`と`v`に渡している。

## if文関連のおさらい

### Noneの判定

```python
# これでもいいけどあまりよくない
if is_empty == None:
    print('None')

# Noneの判定はこういうのが自然
if is_empty is None:
    print('None')

# Noneでない判定はこういうのが自然
if is_empty is not None:
    print('not None')
```

## 関数定義関連

### 基本

```python
def hello_hanako():
    print("Hello, Hanako!!")

# 実行
hello_hanako()

# 型を調べるとfunction型になっている
print(type(hello_hanako))

# function型なので、こういう使い方もできるっぽい
hanako_func = hello_hanako
hanako_func()
```

```
Hello, Hanako!!
<class 'function'>
Hello, Hanako!!
```

### 型の定義

```python
def calc_num(a: int, b: int) -> int:
    return a + b

ans = calc_num(10, 20)
print(ans)
```

### デフォルト引数にリストを与えるべきではない

```python
def test_hoge(x , l=[]):
    l.append(x)
    return l

# 返却値として[100]を期待
r = test_hoge(100)
print(r)

# 返却値として[100]を期待しているが..
r = test_hoge(100)
print(r)
```

参照渡しなのでリスト`r`に`100`が追加されてしまう。  

```
[100]
[100, 100]
```


### 位置引数のタプル化

```python
# 位置引数*argsを定義
def say_something(person, *args):
    print('I am ' + person)
    for arg in args:
        print(arg)

say_something('Hanako', 'Taro', 'Shintaro')

# タプルを使って展開もできる
# ただし、あまり使わないらしい。
t = 'Taro', 'Shintaro'
say_something('Hanako', *t)
```

```
I am Hanako
Taro
Shintaro
I am Hanako
Taro
Shintaro
```

### キーワード引数

```python
def my_menu(**kwargs):
    print(kwargs)
    for k, v in kwargs.items():
        print(k, v)

# 書き方その1 よく見る書き方。
my_menu(food='unadon', drink='cola')

# 書き方その2 これもよく見る書き方
entry = {
    'food': 'udon',
    'drink': 'water',
    'dessert': 'ice'
}
my_menu(**entry)
```

### 引数、位置引数、キーワード引数が混在のパターン

```python
# 引数、位置引数、キーワード引数が混在のパターン
# これもよく見る。
# 引数の定義順も大事。引数、,*args, **kwargsの順でないとNG
def my_menu(food, *args, **kwargs):
    print(food)
    print(args)
    print(kwargs)

my_menu('rice ball', 'water', 'cola', dessert1='pan-cake', dessert2='chocolate ice')
```

```
rice ball
('water', 'cola')
{'dessert1': 'pan-cake', 'dessert2': 'chocolate ice'}
```

### inner function(関数内関数)

```python
def outer(a, b):
    # outerの中でしか使わない場合はinner functionがいいこともある。
    def plus(c, d):
        return c + d

    print(plus(a, b))

outer(1, 2)
```

### クロージャー

```python
# クロージャ関数を定義
def square_area_func(length):
    # インナー関数を定義
    def calc(scale):
        return length * length * scale

    return calc # calc()を返すのではなくてインナー関数calcを返す

cal1 = square_area_func(6)
cal2 = square_area_func(7)

# 遅延評価する。ここで初めて計算が実行される
result1 = cal1(1.5) # 6 * 6 * 1.5
result2 = cal2(2.0) # 7 * 7 * 2.0
print(result1)
print(result2)
```

```
54.0
98.0
```

### デコレータ

#### 少しむずかしい方法

```python
def add_num(a, b):
    """
    デコレーション対象のメソッド
    """
    return a + b


def print_info(func):
    """
    デコレータの定義
    :param func: デコレーション対象のメソッド
    :return:
    """
    
    # インナー関数を定義
    def wrapper(*args, **kwargs):
        print("Start..")  # デコレーション(前処理)
        result = func(*args, **kwargs)  # 計算処理
        print("End..")  # デコレーション(後処理)
        return result

    return wrapper  # 関数として返す


deco_f = print_info(add_num)
r = deco_f(10, 30)
print(r)
```

```
Start..
End..
40
```

#### 上記をすこし簡単にした方法

デコレーション対象のメソッド(`add_num`)定義前に`@デコレータメソッド(print_info)`をつけてあげる。

デコレータメソッドの定義方法は変わらないが、使い方が簡単になる。

```python
def print_info(func):
    """
    デコレータの定義
    :param func: デコレーション対象のメソッド
    :return:
    """

    # インナー関数を定義
    def wrapper(*args, **kwargs):
        print("Start..")  # デコレーション(前処理)
        result = func(*args, **kwargs)  # 計算処理
        print("End..")  # デコレーション(後処理)
        return result

    return wrapper  # 関数として返す


@print_info
def add_num(a, b):
    """
    デコレーション対象のメソッド
    """
    return a + b


# デコレーション対象のメソッドを呼び出すのみでデコレーションしてくれる
r = add_num(10, 30)
print(r)
```

```
Start..
End..
40
```

デコレータ自体の定義方法には慣れが必要なので、使い所など含めて要練習。

### ラムダ


```python
def capitalize_words(words, func):
    for word in words:
        print(func(word))


words = ['hanako', 'taro', 'kurara', 'haiji']
capitalize_words(words, lambda w: w.capitalize())
capitalize_words(words, lambda w: w.swapcase())
```

### ジェネレータ

```python
def foods():
    yield "Rice ball"
    yield "Udon"
    yield "Soba"


itr = foods()
print(itr)
print(next(itr))
print(next(itr))
print(next(itr))


def i_love_python(num=5):
    for _ in range(num):
        yield "I love python."


itr2 = i_love_python(3)
print(itr2)
print(next(itr2))
print(next(itr2))
print(next(itr2))
```

```
<generator object foods at 0x10598b1a8>
Rice ball
Udon
Soba
<generator object i_love_python at 0x10598b200>
I love python.
I love python.
I love python.
```

### 内包表記

内包表記を使うと簡潔になるが、複雑にしすぎるとわかりにくくなるので、適材適所で利用する。

#### リスト内包表記

```python
# 基本
foods = ("ハンバーグ", "おにぎり", "ラーメン", "うどん")
foods_list = [food for food in foods]
print(foods_list)

# ifを挟む場合
numbers = (100, 10000, 33333, 5501)
even_number_list = [number for number in numbers if number % 2 == 0]
print(even_number_list)

# リスト同士の掛け算
num1 = [1, 2, 3, 4, 5]
num2 = [5, 6, 7, 8, 9]
mul_num = [i * j for i in num1 for j in num2]
print(mul_num)
```

```
['ハンバーグ', 'おにぎり', 'ラーメン', 'うどん']
[100, 10000]
[5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 18, 21, 24, 27, 20, 24, 28, 32, 36, 25, 30, 35, 40, 45]
```

#### 辞書内包表記

```python
week = ['mon', 'tue', 'wed']
drinks = ['coffee', 'milk', 'cola']

# 内包表記を使わない記述
d = {}
for day, drink in zip(week, drinks):
    d[day] = drink
print(d)

# 内包表記を使った記述
d = {day: drink for day, drink in zip(week, drinks)}
print(d)
```

#### 集合内包表記

```python
# 内包表記を使わない記述
s = set()
for i in range(10):
    s.add(i)

print(s)

# 内包表記を使った記述
s = {i for i in range(10)}
print(s)
```

#### ジェネレータ内包表記

```python
# 内包表記を使わない記述
def generator():
    for _ in range(5):
        yield "Hello Andaconda!"

g = generator()
print(type(g))

# 内包表記を使った記述
g = ("Hello Miniconda" for _ in range(10))
print(type(g))

# 内包表記で生成したジェネレータからタプルを生成する
t = tuple("Hello Miniconda" for _ in range(10))
print(t)

# 内包表記で生成したジェネレータからリストを生成する
l = list("Hello Miniconda" for _ in range(10))
print(l)
```

## 例外処理


### 基本

`try`~`except`~`finally`で記述する。

```python
try:
    print(l[4])

# インデックスエラー例外
except IndexError as ex:
    print("Index Error!!", ex)

# Exceptionを捕捉する。
# これ以上のものは捕捉するべきでない。
except Exception as ex:
    print("まるっと例外を捕捉するのはあまりよくない", ex)

# 例外が発生しなかった場合に実行される
else:
    print("例外が発生しなかった場合に実行")

# 例外が発生しても必ず実行させたい処理
finally:
    print('finish!')
```

例外のクラス階層は以下参照

https://docs.python.jp/3/library/exceptions.html#exception-hierarchy

他言語と同様、例外を発生させるときは`raise`。

```python
raise IndexError('Index errorを発生させました')
```


### 独自例外定義

```python
# 独自例外定義
class MyApplicationException1(Exception):
    pass

# 独自例外定義
class MyApplicationException2(Exception):
    pass


def check(words):
    for word in words:
        if word.isupper():
            # 例外を発生させる
            raise MyApplicationException1(word)
        if word.isdigit():
            # 例外を発生させる
            raise MyApplicationException2(word)


words = ['APPLE', 'orange', 'bananaa']

try:
    check(words)
except MyApplicationException as ex:
    print("あああああああ", ex)
except YourApplicationException as ex:
    print("いいいいいいい", ex)
```

## クラス

### 基本

以下の`self`はオブジェクトを指している。
`person`を示すことになる。

```python
class Person(object):  # python2の名残でobjectを書いた方がいいらしい
    # クラス変数
    # すべてのオブジェクトで共有してもいいもののみに利用する。
    kind = "人間"

    def __init__(self, name):
        """
        これはコンストラクタ
        """
        print("init..")
        self.name = name

    def say_hello(self):
        """
        これはインスタンスメソッド
        """
        print('Hello, {}'.format(self.name))

    @staticmethod
    def run():
        """
        これはスタティックメソッド
        selfを使用しない。
        """
        print('run run run')

    def __del__(self):
        """
        デストラクタ
        オブジェクトが使われなくなったら呼ばれる
        """
        print('good bye')


person = Person('Hanako')  # new演算子なるものはない。
person.say_hello()

# 明示的にデストラクタを呼ぶ場合
del person
```

### オーバーライド/superによる呼び出し

```python
class Car(object):
    def __init__(self, model=None):
        self.model = model

    def run(self):
        print('run')


class SuperCar(Car):
    def run(self):
        # オーバライド
        print('super run')


class UltraCar(Car):
    def run(self):
        # 親クラスのrunを呼び出し
        super().run()
        print('ultra run')


car = Car()
car.run()

super_car = SuperCar()
super_car.run()

ultra_car = UltraCar()
ultra_car.run()
```

```
run
super run
run
ultra run
```

### プロパティを使った属性設定

プロパティを指定してゲッターとセッターを定義するのがいいらしい。プロパティをつけると、わかりやすくなるが少し手間。

```python
class Person(object):
    def __init__(self, name, age):
        # プライベートな属性には慣習的に_を先頭につける
        # ただし、_ひとつだけだと外からもアクセスできるので、きちんと属性を保護したいのであれば、
        # __として2つアンダースコアをつけるようにする
        self._name = name
        self._age = age

    @property
    def name(self):
        """
        ゲッター
        """
        return self._name

    @property
    def age(self):
        """
        ゲッター
        """
        return self._age

    @name.setter
    def name(self, name):
        """
        セッター
        """
        self._name = name

    @age.setter
    def age(self, age):
        """
        セッター
        """
        self._age = age



person = Person("Hanako Yamada", 32)

print(person.name) # propertyがついているので、person.name()としなくていい。
print(person.age) # propertyがついているので、person.age()としなくていい。

person.name = 'Sakamichi Onoda' # セッターを使っている
person.age = 16 # セッターを使っている

print(person.name)
print(person.age)
```

### 抽象クラス

抽象クラスを使って、子クラスでメソッドの実装を忘れないようにさせたい場合に利用する。

Pythonでの抽象クラスの利用はあまり推奨されていないらしい。

```python
# abcをインポートする
import abc

class Person(metaclass=abc.ABCMeta):
    """
    抽象クラス
    """
    def __init__(self, age=None):
        self.age = age

    @abc.abstractmethod
    def walk(self):
        """
        @abc.abstractmethodで子クラスに実装させるようにする
        """
        pass

class Baby(Person):
    def __init__(self):
        self.age = 1

    # このメソッドは実装されていないといけない
    def walk(self):
        print("はいはい")


class Adult(Person):
    def __init__(self):
        self.age = 20
    
    # このメソッドは実装されていないといけない
    def walk(self):
        print("るんるん")
```

### 多重継承

```python
# 人間クラスの定義
class Human(object):
    def talk(self):
        print("ほげほげほげ")

    def walk(self):
        print("てくてく")

# 鳥クラスの定義
class Bird(object):
    def fly(self):
        print("とんでる")

    def walk(self):
        print("ぺたぺた")


# 鳥人間クラスの定義
# 多重継承で定義
class BirdHuman(Human, Bird):
    pass

bird_human = BirdHuman()
bird_human.fly()
bird_human.talk()

# 同じメソッドがある場合、先に継承したクラスのメソッドが呼ばれる
bird_human.walk() 
```

### クラスメソッド

クラス変数を参照するメソッド
`@classmethod`をつける。

```python
class Bird(object):
    # クラス変数
    kind = "鳥"

    def __init__(self):
        pass

    @classmethod
    def what_is_kind(cls):
        # clsは、Birdを指している。
        # Birdクラスから生成されたインスタンスオブジェクトではないことに注意
        print(cls.kind)

# クラスメソッドの実行
# インスタンス化しなくても使えるので、Bird()ではなく、Birdになっている
Bird.what_is_kind()
```

### スタティックメソッド

クラス変数もインスタンス変数も参照せず、インスタンス生成せずに利用できるメソッド。
オブジェクトの状態とかに依存せず、util系のメソッドとかを定義するときとかに使う。`@staticmethod`をつける。

```python
class Bird(object):

    def __init__(self):
        pass

    @staticmethod
    def has_leg():
        print("2本足です")


# スタティックメソッドを利用する
Bird.has_leg()
```

### 特殊メソッド

**メソッド名の前後に`__`がつくメソッド**。
コンストラクタ、デストラクタをの`__init__`、`__del__`も特殊メソッド。

特殊メソッドはいろいろあるが、一番良く使うのが`__str__`メソッド。

```python
class Word(object):
    def __init__(self, text):
        self.text = text

    def __str__(self):
        return "word word word"

    def __len__(self):
        return len(self.text)

    def __add__(self, other):
        return self.text + " is " + other.text

    def __eq__(self, other):
        return self.text == other.text


word = Word("Sunday Magazine Jump")
other_word = Word("週刊少年誌")
other_word2 = Word("Sunday Magazine Jump")

# __str__メソッドが呼び出される
print(word)

# __len__メソッドが呼び出される
print(len(word))

# __add__メソッドが呼び出される
print(word + other_word)

# __eq__メソッドが呼び出される
print(word == other_word)
print(word == other_word2)
```


## ファイル読み書き

### ファイルの書き込み

#### withステートメントを使わない書き方

```python
f = open('test.txt', 'w')
f.write('Test')
f.close()
```

#### withステートメントを使った書き方

ファイルの閉じ忘れがないように基本的には、withステートメントを使うようにする。


```python
with open('test.txt', 'w') as f:
    f.write('Test\n')
```

* `w`: ファイルの新規作成、存在する場合は上書き
* `w+`: ファイルの書き込み+読み込み
* `a`: ファイルが既に存在する場合は追記、存在しない場合はファイルの新規作成
* `a+`: ファイルの書き込み+読み込み。ファイルが既に存在する場合は追記、存在しない場合はファイルの新規作成

### ファイルの読み込み

`read()`でファイルの内容をすべて読み込む。
`readline()`でファイルの内容を一行ずつ読み込む。

```python
with open('test.txt', 'r') as f:
    # f.reaad()ですべて読み込み
    print(f.read()) 
    while True:
        # f.readline()で一行ずつ読み込み
        line = f.readline() 
        print(line, end='')
        if not line:
            break
```

以下のようにしてチャンクサイズを指定して読み込むこともできる。

```python
# チャンクをつかって2文字ずつ読み込みする
with open('test.txt', 'r') as f:
    while True:
        chunk = 2 # チャンクのサイズを2としている
        line = f.read(chunk)
        print(line)
        if not line:
            break

```

`seek()`でファイル内の位置を指定することもできる。

```python
with open('test.txt', 'r') as f:
    print(f.tell()) # f.tell()で現在の位置を返す
    print(f.read(1))
    f.seek(5) # ファイルの位置を先頭から5に設定
    print(f.read(1))
    f.seek(14) # ファイルの位置を先頭から14に設定
    print(f.read(1))
    f.seek(0) # ファイルの位置を先頭に設定
```

### テンプレート

以下のようなテンプレートファイル`design/email_template.txt`があるとして、`$name`と`$contents`にプログラムから値を設定する。

```design/email_template.txt
Hi $name.

$contents

Have a good day
```

このファイルを使って、

1. `string`ライブラリをインポート
2. `string.Template()`関数でテンプレートを読み込んで`t`に設定して
3. `t.substitute()`でテンプレートに値を設定して文字列を返す

コードは以下。

```python
import string

# テンプレートファイルの読み込み
with open('design/email_template.txt') as f:
    t = string.Template(f.read())

# テンプレートの変数に値を設定
contents = t.substitute(
  name='Hanako', 
  contents='How are you? I am fine!')

print(contents)
```

### CSVライブラリ

`import csv`でインポートしてから利用する。

#### ファイルの書き込み

```python
import csv

with open('test.csv', 'w') as csv_file:
    # フィールド名を配列で設定する。
    fieldnames = ['name', 'count']

    # csv.DictWriter()メソッドの引数として、ファイルオブジェクト、フィールド名を渡す
    writer = csv.DictWriter(
      csv_file, ieldnames=fieldnames)

    # ヘッダーの出力
    writer.writeheader()

    # ボディ部の書き込み
    # フィールド名と対応する値をディクショナリ形式で
    # writer.writerow()メソッドの引数として渡す
    writer.writerow({'name': 'A', 'count': 1})
    writer.writerow({'name': 'B', 'count': 2})
    writer.writerow({'name': 'C', 'count': 3})
```

#### ファイルの読み込み

```python
with open('test.csv', 'r') as csv_file:
    # csv.DictReader()メソッドの引数として、ファイルオブジェクトを渡す
    reader = csv.DictReader(csv_file)
    
    # 一行ずつ処理する
    for row in reader:
        print(row['name'], row['count'])
```

### ファイル操作

ライブラリ`os`, `glob`, `shutil`, `pathlib`を使う。
ライブラリ名だけ覚えておいて、あとはググる。

#### `os`

```python
import os

# ファイルが存在しているかをTrue,Falseで返す
print(os.path.exists('test.txt'))

# ファイルかどうかをTrue,Falseで返す
print(os.path.isfile('test.txt'))

# ディレクトリかどうかをTrue,Falseで返す
print(os.path.isdir('design'))

# ファイルのリネーム
os.rename('test.txt', 'renamed.txt')

# シンボリックリンクの作成
os.symlink('renamed.txt', 'symlink.txt')

# ディレクトリの作成
os.mkdir('test_dir')

# ディレクトリの削除
# ただし、ディレクトリの中身が空の場合のみ消せる
os.rmdir('test_dir') 

# ディレクトリの中身をリストで取得する
print(os.listdir('test_dir'))
```

#### `pathlib`

```python
import pathlib

# 空ファイルを作成する
pathlib.Path('test_dir/test_dir2/empty.txt').touch()
```

#### `glob`

```python
import glob

# test_dirディレクトリ内のファイルをリストで取得する
print(glob.glob('test_dir/*'))

# test_dirディレクトリ内の,txtファイルだけリストで取得する
print(glob.glob('test_dir/*.txt'))
```

#### shutil

```python
import shutil

# ファイルのコピー
shutil.copy('test_dir/test_dir2/empty.txt',
            'test_dir/test_dir2/empty2.txt')

print(glob.glob('test_dir/test_dir2/*'))


# os.rmdirだとディレクトリが空の場合でないと消せないので、
# 空でない場合に消したいときにshutil.rmtree()を利用する
shutil.rmtree('test_dir')
```

### ファイルの圧縮、解答

#### tar.gzファイル

`import tarfile`でインポートしてから利用する。

```python
import tarfile

# tarファイルの圧縮
with tarfile.open('test.tar.gz', 'w:gz') as tr:
    # 圧縮したいファイル、ディレクリを追加する
    tr.add('test_dir')

# tarファイルの解凍
with tarfile.open('test.tar.gz', 'r:gz') as tr:
    tr.extractall(path='test_tar')

    # 展開せずに中身を見ることもできる
    with tr.extractfile('test_dir/test.txt') as f:
        print(f.read())
```

#### zipファイル

`import zipfile`でインポートしてから利用する。

```python
import zipfile
import glob

# zipファイルの圧縮
with zipfile.ZipFile('test.zip', 'w') as z:
    # ひとつずつ追加する方法
    z.write('test_dir')
    z.write('test_dir/test.txt')

    # まとめて追加する方法
    for f in glob.glob('test_dir/**', recursive=True):
        print(f)
        z.write(f)

# zipファイルの解凍
with zipfile.ZipFile('test.zip', 'r') as z:
    # test_zipというディレクトリに展開する
    z.extractall(path='test_zip')

    # tarと同様、展開せずにファイルの内容を取得することもできる
    with z.open('test_dir/test.txt') as f:
        print(f.read())
```

### tempfile

`import tempfile`でインポートしてから利用する。
tempfileは一時的なファイルを意味している。

```python
import tempfile

# 一時的なファイルとして作成したい場合に利用する
# 処理が終わったら自動的に削除される
# IO Bufferの用途で利用する
with tempfile.TemporaryFile(mode='w+') as t:
    t.write('hello')
    t.seek(0)
    print(t.read())

# テンポラリファイルを作って残す場合
# NamedTemporaryFileを使い、引数のdeleteをFalseに設定する
with tempfile.NamedTemporaryFile(delete=False) as t:
    print(t.name)
    with open(t.name, 'w+') as f:
        f.write('test\n')
        f.seek(0)
        print(f.read())

# テンポラリディレクトリをつくる場合
# 処理が終わったら自動的に消去される
with tempfile.TemporaryDirectory() as td:
    print(td)

# テンポラリディレクトリを処理が終わっても残す場合
temp_dir = tempfile.mkdtemp()
print(temp_dir)
```

## 外部コマンドの実行

`subprocess.run()`を利用する。
`os.system()`は推奨されていないので利用しないようにする。

```python
import subprocess

# コマンドの実行
subprocess.run(['ls', '-la'])

# 戻り値(結果コード)を取得して判定などしたい場合
rtn = subprocess.run(['ls', '-la'])
print(rtn)
```

### タイムスタンプ、 日付

`import datetime`でライブラリをインポートして利用する。

#### タイムスタンプを扱う場合

```python
import datetime

# 現在の時刻を取得する
now = datetime.datetime.now()
print(now) # datetime.datetime(2018, 8, 30, 1, 10, 57, 15584)

# ISO形式の文字列として出力する
print(now.isoformat()) # 2018-08-30T01:10:57.015584

# 形式を指定して出力する
print(now.strftime('%Y%m%d:%H:%M:%S')) # 20180830:0110:57
```

#### 日付までを扱う場合

```python
import datetime

today = datetime.date.today()

# ISOフォーマットで表示
print(today)
print(today.isoformat())

# 任意のフォーマットで表示
print(today.strftime('%d/%m/%y'))
```

出力結果は以下。

```
2018-08-31
2018-08-31
31/08/18
```

#### 計算

```python
import datetime

now = datetime.datetime.now()
print(now) # 2018-08-30 01:18:09.813074

# 差分
d = datetime.timedelta(weeks=1)
# d = datetime.timedelta(days=1)
# d = datetime.timedelta(hours=24)

# 演算
# この場合は1週間前
print(now - d) # 2018-08-23 01:18:09.813074
```


## コンフィグ

### `configparser`

`import configparser`でインポートして利用する。

#### 書き込み

```python
import configparser

# configオブジェクトの生成
config = configparser.ConfigParser()

config['DEFAULT'] = {
   'debug':  True
}

config['web'] = {
    'host': '127.0.0.1',
    'port':  80
}

config['db'] = {
    'host': '127.0.0.1',
    'port': 5432
}

# ファイル書き込み
with open('config.ini', 'w') as file:
    # writeメソッドで書き込む
    config.write(file)
```

出力される`config.ini`は以下の形式になっている。

```config.ini
[DEFAULT]
debug = True

[web]
host = 127.0.0.1
port = 80

[db]
host = 127.0.0.1
port = 5432
```

#### 読み込み

```python
import configparser

# configオブジェクトの生成
config = configparser.ConfigParser()

# readメソッドでconfig.iniを読み込む
config.read('config.ini')

# 内容の確認
print(config['web'])
print(config['web']['host'])
```

### `yaml`

#### 書き込み

```python
import yaml

with open('config.yml', 'w') as file:
    # dumpメソッドで書き込み
    yaml.dump({
        'web': {
            'host': '127.0.0.1',
            'port': 80
        },
        'db': {
            'host': '127.0.0.1',
            'port': 3306
        }
    }, file, default_flow_style=False)
```

出力結果は以下。

```config.yml
db:
  host: 127.0.0.1
  port: 3306
web:
  host: 127.0.0.1
  port: 80
```

なお、`default_flow_style=False`がない場合には、以下のような形式となってしまう。

```config.yml
db: {host: 127.0.0.1, port: 3306}
web: {host: 127.0.0.1, port: 80}
```

#### 読み込み

```python
import yaml

with open('config.yml', 'r') as file:
    data = yaml.load(file)
    print(data['web']['host'])
```


## ロギング

### 基本

```python
import logging

# ロギングの出力レベルをINFOに設定する。
# デフォルトはWARNING以上になっている。
logging.basicConfig(level=logging.INFO)

# ファイルに出力する場合は、filenameを指定する。
# logging.basicConfig(filename='test.log', level=logging.INFO)

logging.critical('criticalです')
logging.error('errorです')
logging.warning('warningです')
logging.info('infoです')
logging.debug('debugです')
```

コンソール表示結果

```
CRITICAL:root:criticalです
ERROR:root:errorです
WARNING:root:warningです
INFO:root:infoです
```

### フォーマッタ設定

```python
import logging

# フォーマットを指定する。
# ここでは、asctime,levelname,messageを属性として指定する。
# 他の属性情報などは、pythonのドキュメントを参照する。
# https://docs.python.jp/3/library/logging.html#logrecord-attributes
formatter = '%(asctime)s:%(levelname)s:%(message)s'


# basicConfigにフォーマッタを渡す。
logging.basicConfig(level=logging.INFO, format=formatter)

logging.info('infoです。')
```

```
2018-08-31 12:48:16,326:INFO:infoです。
```

### ロガー設定

```main_app.py
import logging

import component_app

# 基本的にメインのアプリでbasicConfigでloggingを設定する
logging.basicConfig(level=logging.INFO)

logging.info('infoです。')

component_app.do_something()
```

```component_app.py
import logging

# ロガーの設定
# サブ側(component_app.py)ではloggingを直接使わずにloggerを使うようにする。
# getLoggerには__name__を慣習的に渡す。
logger = logging.getLogger(__name__)

# レベルをDEBUGに設定
logger.setLevel(logging.DEBUG)

def do_something():
    # main_app.pyではレベルをINFOに設定しているが、
    # component_app.pyではloggerの設定レベルをDEBUGに設定しているため出力される。
    logger.info('info component_app')
    logger.debug('debug component_app')

    # main_appで定義したloggingの設定レベルに依存するので、
    # なるべくloggerを使うようにする。
    # logging.info(info by logging)
```

出力結果は以下。

```
INFO:root:infoです。
INFO:component_app:info component_app
DEBUG:component_app:debug component_app
```

### ハンドラー設定

ハンドラを使うと、出力先の設定を変更できる。
以下はloggerの出力先をファイルに設定した例。

```component_app.py
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# ハンドラの設定
h = logging.FileHandler('component_app.log')
logger.addHandler(h)


def do_something():
    logger.info('info component_app by logger')
    logger.debug('debug component_app by logger')

    # これはloggerを使っていないためcomponent_app.logに出力されない
    logging.info('info component_app by logging')
```

```component_app.log
info component_app by logger
debug component_app by logger
```

ハンドラはデフォルトのStreamHandlerやFileHandler以外にも多くあるので、詳細はPythonドキュメントを参照する。
https://docs.python.jp/3/library/logging.handlers.html

### フィルタ設定

ログメッセージのフィルタリング設定例。

```python
import logging

logging.basicConfig(level=logging.INFO)


# フィルタリングクラスを定義する。
class NoPasswordFilter(logging.Filter):
    """
    パスワードをチェックするフィルタクラス
    logging.Filterクラスを継承してカスタマイズする
    """

    def filter(self, record):
        """
        メッセージをフィルタする
        """
        # レコードからログメッセージを取得する
        log_message = record.getMessage()

        # ログメッセージにpasswordという語句が含まれていたらフィルタリングする
        if 'password' not in log_message:
            return True
        else:
            return False


logger = logging.getLogger(__name__)

# フィルタを追加する
no_password_filter = NoPasswordFilter()
logger.addFilter(no_password_filter)

# passwordが含まれていないのでフィルタリングされない。
logger.info('from main')

# passwordが含まれているのでフィルタリングされる。
logger.info('from main password = "this is secret password"')
```


### コンフィグ設定

ロギングの設定をファイルに定義する。
`import logging.config`でインポートして利用する。

```python
import logging.config

# logging.ini参照
logging.config.fileConfig('logging.ini')

# rootロガーを利用している
logger = logging.getLogger(__name__)

logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')


# simpleExampleロガーを利用する
logger2 = logging.getLogger('simpleExample')

logger2.debug('debug message')
logger2.info('info message')
logger2.warning('warn message')
logger2.error('error message')
logger2.critical('critical message')
```

`logging.ini`の設定例は以下。

```logging.ini
[loggers]
keys=root,simpleExample

[handlers]
keys=streamHandler

[formatters]
keys=formatter


[logger_root]
level=WARNING
handlers=streamHandler

[logger_simpleExample]
level=DEBUG
handlers=streamHandler
qualname=simpleExample
propagate=0

[handler_streamHandler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
```

ポイントは、上3つの[loggers],[handlers],[formatters]で定義しているキー情報。

* [loggers]セクションにはrootキーとsimpleExampleキーを使えるように定義している
    * rootは[logger_root]セクションで設定している
    * simpleExampleは[logger_simpleExample]セクションで設定している
* [handlers]セクションにはhandlerにstreamHandlerキーを使えるように定義している
    * [handler_streamHandler]セクションで設定している
* [formatters]にはformatterにformatterキーを使えるように定義している
    * [formatter_formatter]セクションで設定している。

### ロギングの書き方

#### キー・バリュー形式で出力する

```python
# 基本的な書き方
logger.error('Hoge hoge hoge hoge')

# キーバリューの形式で出力
# ログ解析のアプリやソフトで扱いやすい場合がある
logger.error({'action': 'create', 'status': '99', 'message': 'Hoge failed!'})
```

#### ログ出力ポイントの例

以下のようなメソッドが合った場合、どこにログを出力するべきか。

```python
import csv

data = {'hanako': 22, 'taro': 25}

def save(, filepath):
    columns = ['name', 'age']
    with open(filepath, 'w+') as file:
        writer = csv.DictWriter(file, fieldnames=columns)
        writer.writeheader()

        for name, age in data.items():
            writer.writerow({'name': name, 'age': age})


save('hoge.csv')
```

以下のことが確認できるようにログ出力ポイントを設ける。
システム障害時のトラブルシューティングのしやすさを考慮することを念頭に置くことが大切。

1. saveメソッドが呼ばれたことを確認できるようにする
2. 書き込みが完了したことを確認できるようにする。
3. メソッドの引数に何が与えられたか確認できるようにする。

```python
data = {'hanako': 22, 'taro': 25}

def save(filepath):
    logger.info({
        'action': 'save',
        'filepath': filepath,
        'status': 'run'})

    columns = ['name', 'age']
    with open(filepath, 'w+') as file:
        writer = csv.DictWriter(file, fieldnames=columns)
        writer.writeheader()

        for name, age in data.items():
            writer.writerow({'name': name, 'age': age})

    logger.info({
        'action': 'save',
        'filepath': filepath,
        'status': 'done'})

save('hoge.csv')
```

## virtualenv

virtualenvを使うことによって、pythonのバージョンを使い分けることができる。

### virtualenvのインストール

```
pip install virtualenv
```

### virtualenvの利用設定

#### pyCharmの場合

`Preferences`→`Project Interpreter`→`AddLocal`から設定する。
`New environment`を選択して、以下設定する。

* Location: 新規でつくるvirtualenvの名前
* Base Interpreter: 作成もとのPython

<img width="1081" alt="pycharm1_venv1.png" src="https://qiita-image-store.s3.amazonaws.com/0/203061/e4fada70-5544-0578-0bab-a52c35c10f23.png">


<img width="885" alt="pycharm_venv2.png" src="https://qiita-image-store.s3.amazonaws.com/0/203061/abcd5f13-b9fb-12b7-1ce9-06ff787fb6ca.png">


再度、`Preferences`→`Project Interpreter`で新規で作成したvirtualenvを設定してあげれば完了。

#### コマンドラインの場合

現在のバージョンの確認

```
$ python -V
Python 3.6.3 :: Anaconda custom (64-bit)
```

my_python36という名前で仮想環境を作成する

```
$ virtualenv my_python36
Using base prefix '/Users/nagakuray/anaconda3'
New python executable in /private/tmp/my_python36/bin/python
Installing setuptools, pip, wheel...done.
```

アクティベートして利用できるようにする

```
$ source my_python36/bin/activate
(my_python36) $
```

無効化したい場合はディアクティベートする。

```
(my_python36) $ deactivate
```