Pythonのお勉強～入門編4～

少し時間が経ってしまいましたが[前回](https://qiita.com/merito/items/1a54798e090f6bea9167)からの続き。
[最初](https://qiita.com/merito/items/e46a506ae9a020555203)はここから始まりました。
ここら辺から覚えることがちょっとしんどくなりそう。。。

# 制御フローとコード構造
## 関数定義
「def 関数名」で定義できる。

```python:実行
def say_something():
    print('hi')

say_something()
```

```:結果
hi
```

上から順番にスクリプトを読み込むので、関数定義される前に関数を呼び出そうとするとエラーになる。

```python:実行
say_something()

def say_something():
    print('hi')
```

```:結果
NameError: name 'say_something' is not defined
```

定義した関数はfunctionという型のオブジェクトになっていて、変数に入れて実行することもできる。

```python:実行
def say_something():
    print('hi')

f = say_something
f()
```

```結果
hi
```

引数と返り値も指定できる。

```python:実行
def what_is_this(color):
    if color == 'red':
        return 'tomato'
    if color == 'green':
        return 'greeeeeeeeeeeeeeeeeen!!!!!!!!!'
    else:
        return '何かしらのあれ'

result = what_is_this('green')
print(result)
result = what_is_this('red')
print(result)
result = what_is_this('black')
print(result)
```

```:結果
greeeeeeeeeeeeeeeeeen!!!!!!!!!
tomato
何かしらのあれ
```

## 関数の引数と返り値の宣言
関数の引数と返り値に型ヒントを指定することもできる。
あまり使われなていないらしい。
ただ、例えば引数、返り値ともにintを指定したとして、実際に関数を呼び出す際に引数に文字列を渡してもエラーとはならずに文字列で処理を実行してしまうので注意。ヒントでしかない。

```python:実行
def add_num(a: int, b: int) -> int:
    return a + b


r = add_num(10, 20)
print(r)

# 文字列を渡しても実行できる
r = add_num('aと', 'bだよ')
print(r)
```

```:結果
30
aとbだよ
```

## 位置引数とキーワード引数とデフォルト引数
位置引数とキーワード引数を合わせて使用できる。
どういう場面で組み合わせて使うかあまりイメージわかないなぁ…

```python:実行
def menu(entree, drink, dessert):
    print(entree)
    print(drink)
    print(dessert)

menu('beef', dessert='ice', drink='beer')
```

```:結果
beef
beer
ice
```

デフォルト引数も指定できる。

```python:実行
def menu(entree='tukemen', drink='tukejiru', dessert='gyoza'):
    print(entree)
    print(drink)
    print(dessert)

menu()
menu(drink='beer')
```

```python:実行
tukemen
tukejiru
gyoza
tukemen
beer
gyoza
```

## デフォルト引数で気を付けること
デフォルト引数にリストなどを指定するのはバグに繋がるのであまりよろしくないらしい。

```python:実行
# デフォルト引数でリストを指定した場合、スクリプトの関数が読み込まれた際に一回だけ生成される
# 新しいオブジェクトを生成するわけではないので、この関数のデフォルト引数を使用して複数回実行すると、同じリストに値が追加されていく。
# リストや辞書型はバグに繋がるのでデフォルト引数で与えるべきではないという暗黙の了解があるらしい
def test_func(x, l=[]):
    l.append(x)
    return l

y = [1, 2, 3]

r = test_func(100)
print(r)
print(id(r))

r = test_func(100)
print(r)
print(id(r))

y = test_func(100, y)
print(y)
print(id(y))

r = test_func(100)
print(r)
print(id(r))
```

```:結果
[100]
30499336
[100, 100]
30499336
[1, 2, 3, 100]
30499400
[100, 100, 100]
30499336
```

それじゃデフォルト引数を使用するたびにリストを初期化して利用したい場合はどうするかというとこんな感じ。

```python:実行
def test_func(x, l=None):
    if l is None:
        # None(デフォルト引数)だったらリストを生成
        l = []
    l.append(x)
    return l

y = [1, 2, 3]

r = test_func(100)
print(r)
print(id(r))

r = test_func(100)
print(r)
print(id(r))

y = test_func(100, y)
print(y)
print(id(y))

r = test_func(100)
print(r)
print(id(r))
```

```:実行
[100]
36397640
[100]
44258824
[1, 2, 3, 100]
36397576
[100]
36397640
```

## 位置引数のタプル化
関数で引数の数が増えてくると書くのとかちょっと面倒。
そんなときは引数をタプルにできるんだよｼﾞｮｰｼﾞｨ。

```python:実行
# 「*変数名」で引数をまとめてくれる(タプルにしてくれる)
def say_something(*args):
    # タプルになっている
    print(args)
    for arg in args:
        print(arg)

say_something('Hi!', 'Mike', 'George')
```

```:結果
('Hi!', 'Mike', 'George')
Hi!
Mike
George
```

通常の引数と組み合わせて使うこともできるみたい。

```python:実行
# wordは固定で使いたいけど、他は何個引数があるかわからないよｼﾞｮｰｼﾞｨな時に使えるらしい
def say_something(word, *args):
    print('word =', word)
    for arg in args:
        print(arg)

say_something('Hi!', 'Mike', 'George', "It's Meeeeeeeeeeeeeeee!!!!!!!!!!")

# あまり使わないらしいけどこういった書き方もできる
t = ('Mike', 'Nancy')
say_something('Hey!', *t)
```

```:結果
word = Hi!
Mike
George
It's Meeeeeeeeeeeeeeee!!!!!!!!!!
word = Hey!
Mike
Nancy
```

## キーワード引数の辞書化
同じように辞書型にもできるらしい。

```python:実行
# 「**変数名」で辞書化できる
def menu(**kwargs):
    print(kwargs)
    for k, v in kwargs.items():
        print(k, v)

menu(entree='beef', drink='coffee')

d = {
    'entree': 'beef',
    'drink': 'ice coffee',
    'dessert': 'ice'
}
menu(**d)
```

```:結果
{'entree': 'beef', 'drink': 'coffee'}
entree beef
drink coffee
{'entree': 'beef', 'drink': 'ice coffee', 'dessert': 'ice'}
entree beef
drink ice coffee
dessert ice
```

前項のタプルとかと一緒にも使える。

```python:実行
def menu(food, *args, **kwargs):
    print(food)
    print(args)
    print(kwargs)

menu('banana', 'apple', 'orange', entree='beef', drink='coffee')
```

```:結果
banana
('apple', 'orange')
{'entree': 'beef', 'drink': 'coffee'}
```

## Docstrings
関数の説明記載できる。
関数内に「"""説明"""」と書いていく。

```python:実行
def example_func(param1, param2):
    """example_func document

    Args:
        param1 (int): The first parameter.
        param2 (int): The second parameter.
    Returns:
        bool: The return value.

    """
    print(param1)
    print(param2)
    return True


print(example_func.__doc__)
help(example_func)
```

```結果
example_func document

    Args:
        param1 (int): The first parameter.
        param2 (int): The second parameter.
    Returns:
        bool: The return value.

    
Help on function example_func in module __main__:

example_func(param1, param2)
    example_func document
    
    Args:
        param1 (int): The first parameter.
        param2 (int): The second parameter.
    Returns:
        bool: The return value.
```

## 関数内関数
関数の中に関数を定義できる。
関数内のみで複数回使用するような場合に使用するらしい。

```python:実行
def outer(a, b):

    def plus(c, d):
        return c + d

    r1 = plus(a, b)
    r2 = plus(b, a)
    print(r1 + r2)

outer(1, 2)
```

```:結果
6
```

## クロージャー
関数内の関数(inner)を返却値にすると

```python:実行
def outer(a, b):

    def inner():
        return a + b

    return inner

# functionのオブジェクト情報が表示される
print(outer(1, 2))
```

```:結果
<function outer.<locals>.inner at 0x0000000002A30D90>
```

このような感じでオブジェクト情報が表示される。
関数内の処理は実行されていない。
これを実行するにはこのようにする。

```python:実行
def outer(a, b):

    def inner():
        return a + b

    return inner

# outer内のinnerは実行されていない
f = outer(1, 2)

# ここで初めて実行される
r = f()

# 実行された返り値が表示される
print(r)
```

```:結果
3
```

具体的には↓みたいな使い方をするらしい。

```python:実行
# 面積求める関数
def circle_area_func(pi):
    def circle_area(radius):
        return pi * radius * radius

    return circle_area

# πを2種類使いたい
ca1 = circle_area_func(3.14)
ca2 = circle_area_func(3.141592)

# それぞれのπで計算したい
print(ca1(10))
print(ca2(10))
```

```:結果
314.0
314.1592
```

用途別で同じことをしたいときに使うのだろうか。
とりあえず何となくこういうものっていうことで覚えておいて次。


## デコレーター
定義した関数の前後などに何かしら処理を付け加えたいとかっていうときに使えるらしい。
例えば計算する処理を実行する前後に開始と終了のメッセージを出すようにしたい。

```python:実行
# func実行前にstartとendを出力するデコレーター
def print_info(func):
    def wrapper(*args, **kwargs):
        print('start')
        result = func(*args, **kwargs)
        print('end')
        return result
    return wrapper

def add_num(a, b):
    return a + b

# print_info内のwrapperが入って返ってくる
# wrapper内に記載されているfuncはadd_numが入っている
f = print_info(add_num)

# wrapperが実行され、add_numの結果が返ってくる
r = f(10, 20)
print(r)
```

```:結果
start
end
30
```

これをこんな感じにも書ける。

```python:実行(結果は同じ)
def print_info(func):
    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, 20)
print(r)
```

デコレータを複数定義して使うこともできる。
記述順に上から実行されるので、順番は注意すること。

```python:実行
def print_more(func):
    def wrapper(*args, **kwargs):
        print('func:', func.__name__)
        print('args:', args)
        print('kwargs:', kwargs)
        result = func(*args, **kwargs)
        print('result:', result)
        return result
    return wrapper


def print_info(func):
    def wrapper(*args, **kwargs):
        print('start')
        result = func(*args, **kwargs)
        print('end')
        return result
    return wrapper


# デコレーター複数使うこともできる
# デコレーターの記述順に実行されていく
# print_info→print_more
@print_info
@print_more
def add_num1(a, b):
    return a + b


# add_numと逆
# print_more→print_info
@print_more
@print_info
def add_num2(a, b):
    return a + b


r1 = add_num1(10, 20)
print(r1)
print('##################')
r2 = add_num2(10, 20)
print(r2)
```

```:結果
start
func: add_num1
args: (10, 20)
kwargs: {}
result: 30
end
30
##################
func: wrapper
args: (10, 20)
kwargs: {}
start
end
result: 30
30
```

正直よくわからないのでぐぐったら以下の記事がでてきた。
[Pythonのデコレータについて](https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e)
[Pythonのデコレータを理解するための12Step](https://qiita.com/_rdtr/items/d3bc1a8d4b7eb375c368)

読んだけど理解しきれてないのでとりあえずこういうものっていうことで覚えておく。

## ラムダ
頭文字が大文字だったり小文字だったりするリスト内の値を全て頭文字を大文字に直す処理があるとする。

```python:実行
# 頭が大文字だったり小文字だったりで揃っていない曜日のリスト
l = ['Mon', 'tue', 'Wed', 'Thu', 'fri', 'sat', 'Sun']

def change_words(words, func):
    for word in words:
        # 引数で渡されたfuncの返り値を出力する
        print(func(word))

def sample_func(word):
    return word.capitalize()

# sample_funcを渡して、リスト内の頭文字を大文字に変更して出力する
change_words(l, sample_func)
```

```:結果
Mon
Tue
Wed
Thu
Fri
Sat
Sun
```

word.capitalize()のためだけにいちいち関数定義したくない。
別でword.lower()で全部小文字にしたい！とかもしたいけどそれだけのために関数定義したくない。
そんな時に

```python:実行
# 頭が大文字だったり小文字だったりで揃っていない曜日のリスト
l = ['Mon', 'tue', 'Wed', 'Thu', 'fri', 'sat', 'Sun']

def change_words(words, func):
    for word in words:
        # 引数で渡されたfuncの返り値を出力する
        print(func(word))

# lambda～は先のsample_funcと同じ動きをする
change_words(l, lambda word: word.capitalize())
change_words(l, lambda word: word.lower())
```

```:結果
Mon
Tue
Wed
Thu
Fri
Sat
Sun
mon
tue
wed
thu
fri
sat
sun
```

というように書ける。
関数の引数に関数を渡すときで、今回のように1行くらいの簡単な処理をする場合によく使われるらしい。
コード量も減るし見やすくなるしいいよね。

## ジェネレーター
関数内に「yield 値」と書いたものをジェネレーターとして判断するらしい。
next(関数名)とするとyieldの値が返却される。
どこまで出力したか覚えてくれるようで、nextを繰り返し実行すると順番に処理される。
その次にyieldで定義したものがないのにnextで処理しようとするとこれ以上ないよとエラーになる。

```python:実行
def greeting():
    yield 'Good morning'
    yield 'Good afternoon'
    yield 'Good night'

g = greeting()
# Good morningが出力される
print(next(g))

# Good afternoonが出力される
print(next(g))

# Good nightが出力される
print(next(g))

# ジェネレータで定義されているものがないのでエラーになる
print(next(g))
```

```:実行
Good morning
Good afternoon
Good night
Traceback (most recent call last):
  File "hoge/hoge.py", line 17, in <module>
    print(next(g))
StopIteration
```

これもよくわからなかったのでぐぐったら記事出てきた。
[Pythonのジェネレーターってなんのためにあるのかをなるべく分かりやすく説明しようと試みた](https://qiita.com/keitakurita/items/5a31b902db6adfa45a70)

## リスト内包表記
リストを生成する際に「[i for i in list]」というような感じでリストが作れる。

```python:実行
t = (1, 2, 3, 4, 5)

# このようにforで回してリストに追加して～とやるよりも
r = []
for i in t:
    if i % 2 == 0:
        r.append(i)

print(r)

# 上と同じ処理をこのように書ける
# こちらの方がメモリの消費が少なかったり処理早かったりするらしい
r = [i for i in t if i % 2 == 0]
print(r)
```

```:実行
[2, 4]
[2, 4]
```

こんなのもリスト内包表記で書ける。

```python:実行
t = (1, 2, 3, 4, 5)
t2 = (5, 6, 7, 8, 9, 10)

r = []
for i in t:
    for j in t2:
        r.append(i * j)

print(r)

# 上と同じ処理をこのように書ける
r = [i * j for i in t for j in t2]
print(r)
```

```:実行
[5, 6, 7, 8, 9, 10, 10, 12, 14, 16, 18, 20, 15, 18, 21, 24, 27, 30, 20, 24, 28, 32, 36, 40, 25, 30, 35, 40, 45, 50]
[5, 6, 7, 8, 9, 10, 10, 12, 14, 16, 18, 20, 15, 18, 21, 24, 27, 30, 20, 24, 28, 32, 36, 40, 25, 30, 35, 40, 45, 50]

```

これくらいなら問題ないけど、forを5個や6個とどんどん増やすとわけわからなくなるので、その場合は通常のfor文で処理を書くようにするといいそうな。

## 辞書包括表記
リスト内包表記と同じように辞書もできる。

```python:実行
w = ['mon', 'tue', 'wed']
f = ['coffee', 'milk', 'water']

d = {}
for x, y in zip(w, f):
    d[x] = y

print(d)

# 上と同じ処理になる
d = {x: y for x, y in zip(w, f)}
print(d)
```

```:実行
{'mon': 'coffee', 'tue': 'milk', 'wed': 'water'}
{'mon': 'coffee', 'tue': 'milk', 'wed': 'water'}
```

## 集合内包表記
こちらもリスト内包表記と同じようにできる。

```python:実行
s = set()

for i in range(10):
    if i % 2 == 0:
        s.add(i)

print(s)

s = {i for i in range(10) if i % 2 == 0}
print(s)
```

```:実行
{0, 2, 4, 6, 8}
{0, 2, 4, 6, 8}
```

## ジェネレーター内包表記
ジェネレーターにも内包表記がある。

```python:実行
def g():
    for i in range(10):
        yield i


g = g()
print(type(g))
print(next(g))
print(next(g))
print(next(g))

# 上と同じ処理になる
# ジェネレーターの内包表記は「()」になる。タプルにはならない。
g = (i for i in range(10))
print(type(g))
print(next(g))
print(next(g))
print(next(g))

# タプルの内包表記は「tuple()」にする。
t = tuple(i for i in range(10))
print(type(t))
print(t)
```

```:実行
<class 'generator'>
0
1
2
<class 'generator'>
0
1
2
<class 'tuple'>
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
```

## 名前空間とスコープ
ちょっとだけややこしい。

```python:実行
animal = 'cat'

def f():
    # グローバル変数のanimalを出力
    print(animal)
    # ローカル変数のanimalを見ようとする
    animal = 'dog'
    print('after', animal)

f()
```

```:実行
Traceback (most recent call last):
  File "hoge/hoge.py", line 10, in <module>
    f()
  File "hoge./hoge.py", line 5, in f
    print(animal)
UnboundLocalError: local variable 'animal' referenced before assignment
```

このようにエラーになる。
最初のprint行を削除すると、ローカル変数のanimalが定義されて、無事出力される。
それじゃグローバル変数の中身変えたいというときはどうするか。
「global 変数名」としてグローバル変数のanimalですよと宣言する。

```python:実行
animal = 'cat'

def f():
    # グローバル変数のanimal
    global animal

    # グローバル変数のanimalにdogを入れる
    animal = 'dog'
    print('local', animal)

f()
print('global:', animal)
```

```python:実行
local dog
global: dog
```

ローカル変数を辞書で返してくれるlocals()、グローバル変数を辞書で返してくれるglobals()というものもある。

```python:実行
"""
Test Test #########################################
"""
animal = 'cat'

def f():
    # ローカル変数のanimalにdogを入れる
    animal = 'dog'
    print('local:', locals())

f()
print('global:', globals())
```

```:結果
local: {'animal': 'dog'}
global: {'__name__': '__main__', '__doc__': '\nTest Test #########################################\n', '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000001D90EF0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/xxxxxxxx/hogeProjects/hoge_programing/hoge.py', '__cached__': None, 'animal': 'cat', 'f': <function f at 0x0000000002F40D08>}
```

## 例外処理
「try 処理 except 例外時の処理」
exceptは例外毎に複数書ける。
Exceptionで例外出たら全部これで受け取るというような形はあまりよろしくない。

```python:実行
l = [1, 2, 3]
i = 5

try:
    l[i]
except IndexError as ex:
    print("Don't worry: {}".format(ex))
except NameError as ex:
    print(ex)
print('last')
```

```:実行
Don't worry: list index out of range
last
```

finallyもある。

```python:実行
l = [1, 2, 3]
i = 5

try:
    l[i]
except IndexError as ex:
    print("Don't worry: {}".format(ex))
except NameError as ex:
    print(ex)
finally:
    print('clean up')
```

whileと同じようにelseもある。
elseは例外が発生せずにtry文を抜けた場合にのみ実行される。
例外が発生した場合は実行されない。

```python:実行
l = [1, 2, 3]

try:
    l[0]
except IndexError as ex:
    print("Don't worry: {}".format(ex))
except NameError as ex:
    print(ex)
else:
    # 例外発生していないので実行される
    print('done')
finally:
    print('clean up1')

print('##################')
del l

try:
    l[0]
except IndexError as ex:
    print("Don't worry: {}".format(ex))
except NameError as ex:
    print(ex)
else:
    # 例外発生したので実行されない
    print('done')
finally:
    print('clean up2')
```

```:実行
done
clean up1
##################
name 'l' is not defined
clean up2
```

## 独自例外の作成
「raise 例外名(メッセージ)」で例外発生させることができる。

```python:実行
# クラス名(継承元クラス)で継承できる
class UppercaseError(Exception):
    pass

def check():
    words = ['APPLE', 'orange', 'banana']
    for word in words:
        if word.isupper():
            raise UppercaseError(word)

check()
```

```:結果
Traceback (most recent call last):
  File "hoge/hoge.py", line 12, in <module>
    check()
  File "hoge/hoge.py", line 10, in check
    raise UppercaseError(word)
__main__.UppercaseError: APPLE
```

もちろんtryで独自例外を使える。

```python:実行
# クラス名(継承元クラス)で継承できる
class UppercaseError(Exception):
    pass

def check():
    words = ['APPLE', 'orange', 'banana']
    for word in words:
        if word.isupper():
            raise UppercaseError(word)
try:
    check()
except UppercaseError as ex:
    print('This is my fault . Go next')
```

```:実行
This is my fault . Go next
```

途中でペニーワイズが出てきたけどきっと眠かったか疲れてた。
続きはまた後日