社内勉強会で教えた｢Python初歩｣の内容

## 背景

現職はユニケージ開発手法を採用しているため、ユニケージ(とフロントエンド用のJavascript)しかプログラミング経験がない方々が何人かいます。以下は社内勉強会でPythonを教えた時の講義内容です。
(社内情報に関するものは内容をボカしています)

---

## 今よりプログラミングを学ぶ理由

* 楽をするため
* リリースを早くするため
* 品質を上げるため(障害を減らす)
* 人を雇うため
  * 超売り手市場の現在、ユニケージでもOKのエンジニアを雇うのは困難になっている

---

## bashの強さは何か

* ファイルの入出力が異常に楽
* linuxコマンドをパイプで繋ぐのがすごい便利
* バックグラウンド処理は `&` だけなのですごい楽
* 実行ログを吐かせるのが簡単

---

## bashの弱さは何か

* 配列が扱いづらい
* 関数が扱いづらい
* 四則演算が扱いづらい
* ifを書くのがとてもめんどくさい
* sqlを扱うのがつらい

---

## Pythonを使って教えること

* 型
* 配列(リスト)
* タプル
* 連想配列(辞書型)
* 制御構文(if,forなど)
* 関数
* スコープ
* リスト内包表記
* 例外処理
* ファイルの入出力

変数、四則演算はさすがにわかると思うのでカット

---

## まずはPythonをインストール

https://qiita.com/ms-rock/items/72b8f1abc661c539bb09 を参考にインストール

---

## python2と3

2はもうすぐ滅びます。3を使いましょう。

macにはデフォルトで古い2が入っているので、3を動かすときは `python3` とします。

```bash
 $ python --version
Python 2.7.15
 $ python3 --version
Python 3.7.2
```

---

## jupyter notebookをインストール

`pip3 install jupyter`

pipコマンドはPythonの各種パッケージをインストールするのに使います

macでpipはデフォルトの2系を向いているため、pip3で3系としてパッケージをインストールします。

---

## jupyterを起動

`$ jupyter notebook` でPythonをブラウザ上実行していきます。

---

## 型

* bool (真偽値)
* int (整数)
* float (浮動小数点)
* string (文字列)
* list (配列)
* dict (辞書)

他にもあります

---

### 型の違いを意識しましょう

* int型の3とstring型の3は違います。 3と"3" で区別します
* int型の2とfloat型の2は違います。 2と 2.0 で区別します
* bool型のTrueと string型のTrueは違います。 True と "True" で区別します

---

### bool型

bashにはない。javascriptにはある

`True` , `False` で表す。 文字の `"True"` , `"False"` とは異なる。

```py
flag = False

if flag: # flag == True と書かなくてもOK
  print("Trueです")
else:
  print("Falseです")
```

---

### 型でハマるケース

文字の3は数字の3ではありません。よって足すことができません。

```python
In [52]: a = "3"
In [53]: a + 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-ca42ed42e993> in <module>
----> 1 a + 1

TypeError: can only concatenate str (not "int") to str

```

---

数字に変換することで足せます

```python
In [54]: int(a) + 1
Out[54]: 4
```

---

文字列結合をするときは、文字列に変換します

```python
In [56]: a = 3
In [57]: a + "です"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-57-c4299ce7ef6f> in <module>
----> 1 a + "です"

TypeError: unsupported operand type(s) for +: 'int' and 'str'

In [58]: str(a) + "です"
Out[58]: '3です'
```

---

## 配列

複数の値を1つの変数に格納したもの

```python
test = [1,2,3] # 配列(正確にはリスト)
print(test[0]) 
print(test[1])
print(test[2])

num = len(test) # 配列の要素数
print( "配列の要素数は" + str(num) + "です。") # +で文字列連結
```

pythonは配列とリストを厳密には区別してます。

---

## タプル

簡単に言えば書き換えられない、追加できない配列です。

```python
In [61]: a = (1,2,3)

In [62]: a[0]
Out[62]: 1

In [63]: a[0] = 2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-63-fa1ba3f7cc8f> in <module>
----> 1 a[0] = 2

TypeError: 'tuple' object does not support item assignment
```

---

### 配列があればいいのでは？タプルいるの？

* 制約がある代わりにタプルのほうが高速、メモリ消費量が少ないとされています

---

## 連想配列

* Pythonでは辞書(dict)と呼びます
* キーと値を持つデータ構造です
* 辞書型→JSONに変換したり、HTMLからの渡ってきたデータが辞書型で格納されているのでよく使います

---

### 例

```python
In [70]: md = {
      "s-san" : "teamA",
      "a-san" : "teamB",
      "n-san" : "teamC"
       }

In [71]: md["s-san"]
Out[71]: 'teamA'

In [72]: md["a-san"]
Out[72]: 'teamB'
```

---

### 何が便利なの？

変数で動作を切り替えやすい

```python
# 指定した人を1日だけteamCチームに送り込む処理
name = "s-san"

(中略)

md[name] = "teamC"  # よろしく
```

---

## 制御構文

* if
* for
* while
* with

Pythonにはswitchがありません！(大きな欠点)

---

### インデント

* 制御構文や関数など入れ子になるゾーンはインデントで表す。
* pythonは括弧やendとかfiを使わない代わりにこういう制約がある
* インデントが必要なところは `:` を使う
* 基本スペース2か4のどっちか

```py
# NG
if flag == False:
print("hoge")

# OK
if flag == True:
    print("hoge")
```

---

### if

* お馴染み。なのでPython固有のものを

```py
if name in ("a","b","c"):
    print("MDチーム")
elif name in ("d","o","k"):
    print("POSチーム")
else:
    print("どこ？")
```

`if ~ in リストorタプル` でどれかにマッチした時

---

```py
if 0 < a < 10:
  print("0より大きく10未満")

if team == "md" and position == "leader":
  print("Aさんかな？")

if team == "md" or team == "crm":
  print("内製")
```

---

`if len(a) == 0` とか `if flag == True` とかは不要である

```py
if not len(a):
  print("空")

if flag:
  print("True')
```

pythonでは 0、空配列、""、None は if文でFalseとして扱われます。 `bool(0)` とか試すとわかります

---

### for

* お馴染み

```py
nums = [1,2,3,4,5]
for n in nums:
  print(n)

# 0から99までの合計(rangeは100回実行するとかにも使えますね)
sum = 0
for i in range(100):
  sum += i

```

---

### while

```py
i = 0
while True:
  i += 1
  if i >= 10:
    break # ループを抜ける

```

---

### with

これは説明が難しいがとても便利

withを使うと、withから抜けたときに開いてあったものを自動で閉じてくれる。(bashには馴染みがないが一般的なプログラミング言語は外部ファイルやDBと接続するときは open処理とclose処理が大抵必要)

詳しくは https://techacademy.jp/magazine/15823

---

## 関数

* ユニケージ開発ではあんまり使われてません。
* そもそも任意の返り値を返せないbashの関数はとても使い勝手が悪いです( 仮引数が `$1` とかもそう… )
* 関数で処理を共通化、分離させるのが一般的です

---

### pythonの基本的な関数

```py
def changeName(name):
    if type(name) is not str:  # ifはこうも書ける
        return "文字列じゃないよ"

    return name.upper()

changeName("ansai")  # => "ANSAI"
changeName(12312312) # => "文字列じゃないよ"
```

---

```python
# 引数は複数取れる
def changeName(firstname,middlename,lastname):
    pass # (略)

# 返り値を複数にもできる
def changeName(name1,name2)
    return name1.upper(), name2.upper()

name1,name2 = changeName("a-san","s-san")
```

---

### 配列やタプルを引数や返り値にできる

```py
def func1(list):
    print(list[0])

func1(["a","b","c","d"]) # 配列を渡す
```

```py
def func1():
    list = ["a","b","c","d"]
    return list # 配列を返す

result = func1()
print(result)
print(result[2])
```

---

### 関数デフォルト値

```python
def joinTeam(name,md_team=False):
    pass # (略)

joinTeam("Mさん")
```

引数は `md_team` は省略可能であり、この場合初期値 `False`

---

### 関数の中の関数(インナー関数)

```python
def outer():
    # 外の関数

    def inner():
        # 関数の中の関数

```

* outerの中からinnerは呼べるが、outerの外からはinnerは呼べない。outer関数以外で使わない関数(使ってほしくない関数)のときにこういう書き方をする。 outer関数の中で似たような処理を繰り返すときに使う。

---

## スコープ

* 変数や関数が見える範囲のこと。スコープの外にあるものは見えない
* bashにもあるが、ユニケージ開発では基本意識されない

---

### Q. このとき表示される値s1は何？

```py
s1 = 0
def localfunc():
    s1 = 2
    print(s1)
    
localfunc()
```

---

### A. 2(localfunc内の変数s1を参照するから)

---

### Q. s2は？

```py
s2 = 0
def localfunc():
    print(s2)
    
localfunc()
```

---

### A. 0(localfunc内にs2がないので外のs2を見に行く)

---

### Q. s3は？

```py
s3 = 0

def localfunc2():
    s3 = 2

def localfunc():
    print(s3)
    
localfunc2()
localfunc()
```

---

### A. 0(localfunc2のs3は関数内でのみ有効、外のs3を見る)

* 関数内の変数と関数外の変数は別な変数である

---

### Q. s4は？

```py
def localfunc():
    s4 = 2
    
localfunc()
print(s4)
```

---

### A. エラーになる(関数の外にs4は存在しない。未定義変数はprintできない)

---

### というのがスコープ

* 変数が定義された場所や定義方法で変数の有効範囲が変わる
* グローバルスコープ: 全域で参照可能(グローバル変数)
* ローカルスコープ: 一定の範囲内(例:関数)でのみ参照可能(ローカル変数)
* スコープの動きは言語によって微妙に違うので注意

---

### コラム: Javascirptのブロックスコープ

* 関数よりも更に狭い、ifやforの範囲内のスコープのこと
* ES6からlet、const使うことでブロックスコープが使えます
* Pythonにはありません :innocent: 

```javascript
for ( let i = 0; i < 10 i++){
  console.log(i); // iはforの範囲のみ有効
}
```

---


### 別ファイルで定義した変数や関数

importしないと参照できない

```hogehoge.py
s = 1

def func():
  pass
```

```sample.py
import hogehoge
print(hogehoge.s)

hogehoge.func()

```

---

### なぜこんな不便なことを？

* 全てがグローバルのとき、大規模になると変数がダブることがありえる
  * いちいちgrepしないと怖くて変数を定義できない
* 人のコードを読む時、このグローバル変数がどこか違うところで使われていないか不安になる
  * グローバル変数は保守性を下げる
* 一般的にグローバル変数は極力使うなとされています

---

## リスト内包表記

リスト内包表記はPythonの特徴的な機能で、リストから別のリストを作るものです。とても便利

---

### 例: 1から100までのリストを作り、そこから偶数のみを抜き出したリストを作りたい

---

こう？

```py
numlist = range(1,101)
evenlist = []

for i in numlist:
  if i%2 == 0:
    evenlist.append(i)

print(evenlist)
```

---

### リスト内包表記ならもっと短く書ける

```py
numlist = range(1,101)
evenlist = [i for i in numlist if i%2==0]
print(evenlist)
```

```py
出力先リスト = [appendする値 for リストから抜き出す値 in リスト if 条件式 ]
```

リストからifにマッチしたものだけを抜き出して別のリストを作っている。ちなみにこっちのほうが若干速い

---

### 複雑にすると読みづらいので多用はやめよう

---

## 例外処理

* エラー時に行う処理である
* 意図しない動作が起きると `例外` が発生する
* Javascriptを始め、いろんな言語にある機能です
* bashは `trap` があるが大分勝手が違う

---

### 例 数字に文字を足す

型の説明で出てきたやつ

```python
In [52]: a = "3"
In [53]: a + 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-ca42ed42e993> in <module>
----> 1 a + 1

TypeError: can only concatenate str (not "int") to str

```

`TypeError` と表示されていますね。  つまり型が合わないので ｢ `TypeError` 例外が発生した｣ わけです。(例外を投げる、例外を吐くなどとも言います。)

---

### 例外発生時にそれを拾う

```py
a = "3"
try:
    a + 1
    print("正常終了")
except TypeError:
    print("TypeError例外が発生しました")
```

tryの中で発生した例外はexceptで拾う(キャッチ)することができます。juptyerで試してみましょう

---

### 例外の詳細を見る

```py
a = "3"
try:
    a + 1
    print("正常終了")
except TypeError as e:
    print(e)
    print("TypeError例外が発生しました")
```

`as 変数` でエラー詳細を吐かせることができます。慣例的に `e` `err` が使われます

---

### 例外は上に伝播する

関数の中で発生した例外をキャッチしなかったとき、その関数を呼んでいる上の関数に例外が伝わります。多くの子関数を呼び出すとき例外処理を都度書かずに親関数でまとめて拾うというアプローチができます

---

### 例

```py
def func1(num):
    return func2(num)
    
def func2(num):
    return num + 1
    
func1(4) # 正しい
func1("4") # 間違い
```

↓ のようにするとfunc2の例外をfunc1でキャッチできる

```py
def func1(num):
    try:
        return func2(num)
    except TypeError:
        print("例外発生")

def func2(num):
    return num + 1
    
func1("4")
```

---

### あらゆる例外をキャッチしたい

`except Exception` と書くとあらゆる例外をキャッチできます。例外の内容に従ってエラーハンドリングができるのでよく考えて使い分けましょう。

```py
try:
    a + 1
except Exception:
    print("例外発生")
```

---

## ファイルの入出力

bashと違ってファイルの入出力はちょっとめんどくさいです。

---

### 書き込み

```py
s = "test1\ntest2\ntest3"

with open('test.txt', mode='w') as f:
    f.write(s)
```

先程説明してきた `with` がここで登場しました。 withの中がファイルをオープンしている区域であり、この外に出るとファイルを自動でクローズします。

---

### 末尾に追加

```py
s = "\ntest4\ntest5\ntest6"

with open('test.txt', mode='a') as f:
    f.write(s)
```

---

### 読み込み

```py
with open('test.txt', mode='r') as f:
    r = f.read()
    print(r)
```

`readlines` を使うと読み込んだ内容を配列にします

```py
with open('test.txt',mode='r') as f:
    r = f.readlines()
    print(r)
    
    print(r[2])  # 配列なので任意の行を取得
    print(r[2].strip()) # 改行を排除するときは strip() で
```

---

ファイルの入出力については詳しくは [Pythonでファイルの読み込み、書き込み（作成・追記）](https://note.nkmk.me/python-file-io-open-with/) を読んでみましょう

---

## 更にPythonを学ぶには

* わからなくなったら、ぐぐればいっぱい出てきます
* [Python入門！初心者がPythonを勉強する学習サイトおすすめ15選](https://qiita.com/kita33/items/8891c7c04b664e7669bf)

---

## 演習問題

Q. この年も某イベントの深夜番を決めることになった。MDチームの中から2名選出しないといけないが、既にNさん、Dさんの2名はプレイベントの深夜番として除外されている。 

MDチームの中から2名ランダムに選ぶ関数を作成しなさい。なおMDチームのメンバーのマスタは既にテキストファイルで用意されており、これを書き換えることはできない。除外される2名を最初に指定する必要があり、本イベントにこの2名を選出してはならない。
(マスタファイルは別途送ります)

---

### 解答レベル

* レベル1: とりあえずランダムに2人選ぶ
* レベル2: とりあえずランダムに2人選ぶが同じ人が選ばれないよう工夫をしている
* レベル3: 別々の2人を選び、除外者2名は選出されない
* レベル4: 関数の中身のコードを変えずにCRMやPOSチームの名簿and人数不定の除外リストに差し替えても動く
* レベル5: 除外リストが空だったり、チームメンバーのマスタファイルが存在しないときに適切に例外処理を行い適切なエラーメッセージを返す。

---

### ルール

関数の形は最初からこちらで指定します。

```py
'''
以下の形であること。引数や返り値の数をいじるのはNG。
第一引数は チームメンバーのマスタを返す関数
第二引数は 除外される2名
'''
member1,member2= electMember(getMemberList(),pre_member)
```

つまり electMember関数とgetMemberList関数を作る必要があります。

ランダムな数字の作り方は https://note.nkmk.me/python-random-randrange-randint/ を見て調べましょう。