Pythonビギナーズガイド（関数編）

# 概要
何回かに渡り、python3の初学者向けの記事を書こうかと思います。
基本的に[オライリー本の入門Python3](https://www.oreilly.co.jp/books/9784873117386/)を購読して、まとめた記事です。
自分の復習用のメモですが、誰かの役に立つかも・・・と思い、投稿しました。
読者はPythonプログラミング初級者を想定しております。

今回は、第4章「コード構造」についてまとめます。

## 第４章：コード構造

* 内包表記
* `*`（位置引数のタプル化）や`**`（キーワード引数の辞書化）など、引数の表記

個人的には上記内容を中心に振り返りました。
また、意外と曖昧にしている「Noneの扱われ方」についても認識がずれていないか確認。

---
この章は学習することが盛り沢山なので、目次を書きます。
(この章は学習内容が多いので、一部別の章でまとめる)
####目次
* 条件分岐・繰り返し処理
* zip()・range()
* 内包表記
* Noneの扱われ方
* Pythonの関数で使用する引数達
* ラムダ関数（無名関数）とクロージャ
* 例外処理

< 補足事項 >

* 継続文字は、”\"（バックスラッシュ）を用いる
* <Font color='red'>3項演算子が特殊</Font>

```py3
foo = x if (x >= 0) else -x; //Pythonの3項演算子
```
```c
foo = (x >= 0) ? x : -x; //c,java,jsなどの3項演算子
```
---

####条件分岐・繰り返し処理

* Pythonの条件分岐は`switch`がなく、`if`を使用
* Pythonの繰り返し処理は`while`と'for'を使用
* PEP8の推奨スタイルではインデントに4個のスペースを使う
* ループの中止に`break`、次のイテレーション開始に`continue`を使用

```py3
if 条件式:
    処理
elif 条件式:
    処理
else:
    処理
```

```py3
while 条件式:
    処理
```

<Font color='red'>※ ブール値のFalseは必ずしも明示的にFalseであるわけではない。</Font color>

|Falseとみなされる|値|
|:---:|:---|
|ブール値|False|
|null|None|
|整数のゼロ|0|
|floatのゼロ|0.0|
|空文字列|''|
|空リスト|[ ]|
|空タプル|( )|
|空辞書|{ }|
|空集合|set( )|

### zip(),range()
pythonライクな書き方は`for ~ in ~`を使うところだろう。pythonはイテレータを頻繁に使うのを好む
C++などだと配列数を提示し、ループをまわすのに対し、イテレータで実装できるメリットは２つある。
* データ構造がどの程度のサイズなのか知らなくても、データ構造の各要素を操作できる。
* メモリを考えずコードが組めるだけでなく、高速

上記の理由のためPythonでは`while arg_size < len(args)`のような形で記述されていることは少なく、while文を見かけることが少ないのだ。
その代わりに`for val in args`といったカンジの表現が多い。

forループの巧妙なテクニックとしてzip()と組み合わせて、複数のシーケンスを並列的に反復処理できる。

```py3
japanese = ('月', '火', '水', '木', '金', '土', '日')
english = ('Monday', 'Thuesday', 'Wednesday', 'Thirsday', 'Friday', 'Saturday', 'Sunday')
list(zip(japanese, english))
print(list(zip(japanese, english)))
# >>[('月', 'Monday'), ('火', 'Thuesday'), ('水', 'Wednesday'), ('木', 'Thirsday'), ('金', 'Friday'), ('土', 'Saturday'), ('日', 'Sunday')]
dict(zip(japanese, english))
print(dict(zip(japanese, english)))
# >>{'月': 'Monday', '金': 'Friday', '土': 'Saturday', '水': 'Wednesday', '日': 'Sunday', '木': 'Thirsday', '火': 'Thuesday'}
```

forループのステップ数などを指定するテクニックとして、range()を使用する。

```py3
for x in range(0,3) #0~3の位置に属する配列・文字列を操作
for x in range(2,-1,-1) #2から0まで逆順で取得
for x in range(0,10,2) #0から10まで2stepで取得
```

####内包表記
内包表記は一つ以上のイテレータからPythonデータ構造をコンパクトに作れる形式。
リスト・タプル・辞書・セットで使用可能。
内包表記は便利だし普通に使うが、条件・繰り返しが多いと個人的には読みづらい。

* リスト内包表記の例

```p3
# 内包表記を使用しない例
num_list = []
for n in range(0, 10):
    num_list.append(n * 2)
# 内包表記を使用する例
num_list2 = [n * 2 for n in range(0, 10)]

# 内包表記を使用しない例
num_list3 = []
for n in range(0, 10):
    if n % 3 == 0:
        num_list3.append(n)
# 内包表記を使用する例
num_list4 = [n for n in range(0, 10) if n % 3 == 0]

# 内包表記を使用する例(numpyを使用しない静的多次元配列確保)
num_list5 = [[1 for n in range(0, 10)] for m in range(0, 5)]
print(num_list5)
```

* 辞書内包表記の例
`{key_item:value_item for item in iterable}`がスタンダード
こちらの挙動だが`dict(zip(key,val))`と勘違いしやすいので注意

```p3
# 同じ挙動をすると勘違いしやすい例
key_list = ('a', 'b', 'c', 'd', 'e')
value_list = [1, 2, 3, 4, 5]
dict1 = dict(zip(key_list, value_list))
print(dict1)
# >> {'c': 3, 'e': 5, 'b': 2, 'a': 1, 'd': 4}

dict2 = {key_list: value_list for key_list in value_list}
print(dict2)
#  >> {1: [1, 2, 3, 4, 5], 2: [1, 2, 3, 4, 5], 3: [1, 2, 3, 4, 5], 4: [1, 2, 3, 4, 5], 5: [1, 2, 3, 4, 5]}

dict3 = {key_list: value_list for key_list in value_list.index()}
print(dict3)
#  >> {1: [1, 2, 3, 4, 5], 2: [1, 2, 3, 4, 5], 3: [1, 2, 3, 4, 5], 4: [1, 2, 3, 4, 5], 5: [1, 2, 3, 4, 5]}
```
* 集合内包表記の例
`{item for item in iterable}`がスタンダード

```p3
set1 = {number for number in range(1,6) if number % 3 == 1)
print(set1)
#  >> {1,4}
```
* ジェネレータ内包表記
タプルには内包表記がない。
リスト内包表記の角括弧を普通の括弧にかえると、ジェネレータ内包表記となる。

####Noneの扱われ方
条件式の中でよく見かけるNoneの仕様を再確認する。
* is None　と　==Noneでは意味が違う。

```py3
thing = None
if thing:
    print('It is some thing')
else:
    print('It is no thing')
#  >>'It is no thing'


def is_none(thing):
    if thing is None:
        print('It is None')
    elif thing:
        print('It is True')
    else:
        print('It is False')

is_none(None)  # It's None
is_none(True)  # It's True
is_none(False)  # It's False
is_none(0)  # It's False
is_none(0.0)  # It's False
is_none(())  # It's False
is_none([])  # It's False
is_none({})  # It's False
is_none(set())  # It's False
```

####Pythonの関数で使用する引数達
* 位置引数
* キーワード引数
* デフォルト引数

<Font color='red'>※　Pythonの関数は値渡しではなく、参照渡し。</Font color>

* 位置引数：
> 先頭から順に対応する位置の仮引数に、実引数を関数に渡す方法。
> 関数を呼び出すときは、個々の引数の位置を覚えていないと使用できない。


* キーワード引数：
> 位置引数は非常によく使用するが、個々の引数の位置を覚えていないと使用できないという欠点がある。
> それを補うように、Pythonでは位置ではなく仮引数の”キーワード”を指定して、実引数を関数に渡す方法。

* デフォルト引数：
>  仮引数にデフォルト値を指定することができる。
> デフォルト引数を指定することにより、引数を省略した時に自動で入る引数を設定できる。

```py3
# 位置引数とキーワード引数の理解
def func(arg1, arg2, arg3):
    処理
    return 返り値
func("hoge", "huga", "hage") # 位置引数（arg1="hoge", arg2="huga", arg3="hage"で参照渡し）
func(arg2="huga", arg3="hage", arg1="hoge") # キーワード引数（arg1="hoge", arg2="huga", arg3="hage"で参照渡し）

# デフォルト引数の理解
def func2(arg, opt_arg=True): #opt_argはデフォルトでTrue。(引数を省略した場合、opt_arg=True)
    return {"arg" : arg, "opt_arg" : opt_arg}
print(func2("Taro")) # {"arg" : "Taro", "opt_arg" : True}
print(func2("Hanako",False)) # {"arg" : "Hanako", "opt_arg" : False}
```

<Font color='red'>※ デフォルト引数を使用するときには、注意点がある。</Font color>
デフォルト引数の値が計算されるのは、関数が実行された時ではなく、定義された時である。
そのため、リスト・辞書・集合などミュータブルなデータ型をデフォルト引数として使用する場合、デフォルト引数が書き換わるので注意が必要。(非推奨)

```py3
# 思った挙動にならないdefult引数
def buggy(arg, results=[]):
    results.append(arg)
    print(results)
buggy('a') # ['a']
buggy('b') # ['a', 'b']となり['b']でない

# 上記挙動の回避
def buggy(arg, results=None):
    if results is None:
        results = []
        results.append(arg)
        print(results)
buggy('a') # ['a']
buggy('b') # ['b']となる
```

* `*`による位置引数のタプル化

> Pythonの引数で使用する`*`はポインタではない。
> 関数定義の中で仮引数の一部として`*`を使うと、可変個の位置引数をタプルにまとめて、その仮引数にセットする。
> 位置引数の最後に`*`による位置引数のタプル化を行うことで、必須引数以外をまとめることができる。
> `*`を使用するときにタプル仮引数をargsと書くのが慣習的です。

```py3
def print_args(*args):
    print('args : ', args)
print_args() # ('args : ', ())
print_args('H','E','L','L','O') # ('args : ', ('H', 'E', 'L', 'L', 'O'))
```
* `**`によるキーワード引数の辞書化

> Pythonの引数で使用する`**`はポインタのポインタではない。
> 関数定義の中で仮引数の一部として`**`を使うと、可変個のキーワード引数を辞書にまとめて、その仮引数にセットする。
> 位置引数の最後に`*`による位置引数のタプル化を行うことで、必須引数以外をまとめることができる。
> また、必須引数以外をまとめる時は`*`による位置引数のタプル化の後に、`**`によるキーワード引数の辞書化を行う必要がある。
> `**`を使用するときに辞書仮引数をkwargsと書くのが慣習的です。

```py3
def print_kwargs(**kwargs):
    print('kwargs : ', kwargs)
print_kwargs() # ('kwargs : ', {})
print_kwargs(luffy='gum', zoro='sword', sanji='leg') # ('kwargs : ', {'zoro': 'sword', 'sanji': 'leg', 'luffy': 'gum'})'leg'}
```

####ラムダ関数（無名関数）とクロージャ
* Pythonでは関数内関数を使用できる(関数の中で関数を定義)
* Pythonでは、「lambda式」を使って無名関数を作ることができる

```py3
myfunc = lambda x, y: x * y # 下記の関数と同値
def myfunc2(x, y):
    return x * y
```
* Pythonでは、クロージャを使用することができる

```py3
def getCounter(init_count = 0):
    count = init_count
    def  counter(reset=False):
        nonlocal count
        if reset:
            count = 0
        count += 1
        return count
    return counter

c = getCounter()
print(c())  # => 1
print(c())  # => 2
print(c())  # => 3
```

####ジェネレータ
関数で`return`の代わりに`yield`を使用することで、ジェネレータ関数を実現できる。
ジェネレータ関数は、イテレータと同じインタフェースを持つ呼び出し可能オブジェクトを返す関数。
各yield文の所まで実行した状態を保存するスタックフレームを保持し、イテレータのnext()が呼び出されると、Pythonは保存されたフレームを復帰し、次のyield文に到達するまで実行される。

```py3
def getCounter(first=0, end=100):
    count = first
    while count < end:
        yield count
        count += 1

counter = getCounter(0,10)
for i in counter:
    print(i)
```

####例外処理

```py3
try:
    # 例外がないかチェックする処理
except exceptiontype as err:
    # 例外が生じた時の処理
finally:
    # 例外の有無に関わらず、処理後に必ず実行される処理
# ちなみに例外を生じさせていときは`raise exceptiontype`などraiseを使用
```

####追記予定内容
* デコレータ
* アンスコ表記
* 名前空間とスコープ