pythonの基礎文法

# pythonの基礎文法

pythonの基礎文法を例を挙げながら説明します。


## コーディング規約

pythonのコーディング規約は、[PEP8](https://pep8-ja.readthedocs.io/ja/latest/)というものがほぼオフィシャルと言えるものがある。<br>
（世の中らのライブラリにはこれに従っていないものも少ならからず存在するが。。。）

PEP8では基本的なことしから記載されていないため、このルールをベースとしてプロジェクト独自のコーディングルールを作っていくとよい。

### 主なルール

* インデントはスペースで４文字（タブは使わない）
* １行の長さは最大79文字（エディタのルーラや整形ツールとかではみ出さないにする）<br>
  ※docstring やコメントは72文字
* [余計な空白は使わない](https://pep8-ja.readthedocs.io/ja/latest/#id15)
* 関数や変数名は小文字のスネークケース
* 定数は大文字のスネークケース（後述するが、pythonに定数という概念はない）
* クラス名はCapWords（先頭大文字のキャメルケース）

## 全体イメージ

細かい例を挙げる前にソースファイルの全体イメージ例を記載する。

### コマンドラインなどがから実行されるスクリプトの例

```python
#!/usr/bin/env python

import sys

# 関数の定義
def func(param):
    print(param)
    return param * 2
    
# メイン
if __name__ == '__main__':
    print(sys.argv) # コマンドライン引数
    func(sys.argv[1])
```

### 共通モジュールのスクリプト例

```python
# クラスの定義
class SampleClass(object):

    CONST_VALUE = 10
    class_field = 'abcde'

    def __init__(self, param):
        self.instance_field = param
    
    def instance_method(self, param):
        pass
    
    @classmehtod
    def class_method(cls, param):
        pass
    
    @staticmethod
    def static_method(param):
        pass

# グローバルな関数
def common_func(param):
    pass
```

プライベートなフィールドやメソッドにする場合には、名前の前にアンダースコアを1個付ける。
アンダースコアを付けても外部からアクセスできてしまうが、約束事として外部からアクセスしないようにする。（紳士協定）

## モジュールのインポート

### python標準やpipで提供されたモジュールの取り込み例

```python
# パッケージ全体のロード
import os
# パッケージの中から特定のモジュールをロード
from datetime import date
# エイリアスを付ける
from datetime import datetime dt
```


### 自作モジュールの取り込み例

自作モジュールは、`setup.py`というファイルを作成してその実行環境下で利用できるモジュールとして登録する方法と自プロジェクト配下のサブディレクトリ配下に共通モジュールを置くケースがあるが、ここでは後者のみについて例を挙げる。

#### ディレクトリ構成
```
MY_PROJECT
│  main.py
│
└─sub_modules
    │  test_lib.py
    │  __init__.py
```

```python:main.py
#!usr/bin/env python

# test_lib.pyをlibという名前で取り込む
import sub_modules.test_lib as lib
# test_lib.py内のcommon_funcを取り込む
from sub_modules.test_lib import common_func

if __name__ == '__main__':
     lib.common_func()
    common_func()
```

```python:sub_modules/test_lib.py
def common_func():
    pass
```

#### `__init__.py`について

ロードされるモジュールの所属するサブディレクトリには、`__init__.py`というファイルを作る必要がある。<br>
中身は空でOKだが、ここに配下のモジュールのインポート処理などを記載することもできる。<br>

## コメントの書き方

* 行コメントは`#`

```python
# これはコメントです
print('hoge') # この形もOK
```

* ブロックコメントは`"""` or `'''`で括る

```python
"""コメント開始
あいうえお
かきくえこ
"""
```

* pydocについては以下のリンク集を参照
* docstringというjavadocコメント的なものがある。<br>
  docstringのコメントの書き方はドキュメント生成ツールとの組み合わせで決まるので
  クラスや関数の定義をどのようなコメントで書くかはツールの仕様次第。<br>

### pydoc/docstringに関するリンク集

* [pydoc](https://docs.python.jp/3/library/pydoc.html)
* [Python pydocの使い方、書き方とhtml出力の方法](https://symfoware.blog.fc2.com/blog-entry-884.html)
* [Pythonのdocstringを使う方法【初心者向け】](https://techacademy.jp/magazine/18695)
* [Google Style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)


## 標準出力

python2系はprintはステートメントで、python3系ではprintは組み込み関数である。

```python
# 普通に
print('abcde')
# 複数を同時に
a = 'A'
b = 'B'
c = 'C'
print(a, b, c) # A B C
print(a, b, c, sep='|') # A|B|C
# 改行を除きたい
print('abcdef', end='')
```

## データ型の基本

```python
# 文字列
# python2系は`u'abcde'`みたいにしないとunicodeにならなかったが、
# python3は全部unicode
str = 'abcde' # 文字列はダブルクォートでもシングルクォートでもOK。
# 真偽値
ok = True
ng = False
# 整数（python3からはintとlongの差はなくなった。（メモリが許す限りの桁数が入る））
int_num = 10
# 浮動小数点（floatとdoubleみたいな違いはない）
float_num = 1.234
# 小数を含む演算するときはDecimalを使う（浮動小数点問題があるから）
from decimal import Decimal
print(0.1*0.2) # => 0.020000000000000004
val1 = Decimal("0.1") # なんでか引数は文字列でなければいけない
val2 = Decimal("0.2")
result = val1 * val2
print(float(result)) # => 0.02

# リスト(配列)
list = [1,2,3,4,5]
list.append(6)
print(list)
# 辞書(dict)
dic = {
    "str": "string",
    "num": 10,
    "arr": [1,2,3,4,5]
}
dic['add_prop'] = 'Additional'
print(dic)

# タプル(一言でいえば、イミュータブル（変更不可能）なリスト)
arr1 = (1, 2, 3)
# arr1.append(4) => エラーになる
print(arr1)
# 要素が１つしかない場合はカンマで終わる
arr2 = (1,)
print(arr2)
arr3 = (1) 
print(arr3) # => 数字の1とみなされる。

# 日時
from datetime import datetime, date
today = date.today()
now = datetime.now()
# 書式変換
today.strftime('%Y/%m/%d')
now.strftime('%Y/%m/%d %H:%M:%S.%f')
```

## 四則演算

概ね、一般的なプログラム言語と違いはないが、インクリメント・デクリメント演算子は無いので`i += 1`のようにする記述する必要がある点は注意。

```python
a = 5
b = 3
# 和
print(a + b) # 8
# 差
print(a - b) # 2
# 積
print(a * b) # 15
# 商
print(a / b) # 1.6666666666666667
# 商の整数部のみ
print(a // b) # 1
# 商の整数部分
print(a % b) # 2
# タブルでまとめて取得
q, r = divmod(a, b) # q=1, r=2
# a += bは可能だが、`++`などのインクリメント演算子は無し
a += b
print(a) # 8
```

## 制御系

### if-elif-else
```python
import random
random_val = random.randint(1,10)
remainder = random_val % 3
if remainder == 0:
    print(f'0:{random_val}')
elif remainder == 1:
    print(f'1:{random_val}')
else:
    print(f'2:{random_val}')
```

### ループ

```python
# forループ
for i in range(10): # => 0-9
    print(i)

# 辞書(dict)のループ
sample_dict = {'a': 'A', 'b': 'B', 'c': 'C'}
# keyのみ
for key in sample_dict: # sample_dict.keys()でも可
    value = sample_dict[key]
    print(f'{key}={value}')

# valueのみ
for value in sample_dict.values():
    print(value)

# keyとvalueのセット
for key, value in sample_dict.items():
    print(f'{key}={value}')

# while
i = 10
while True:
    print(i)
    if i < 1:
        break
    i += -1
# continueもあるけど割愛
```

## 例外処理

書き方こそ違えどほぼjavaと一緒です。

```python
try:
    val = 1 / 0
except:
    print('!!!Exception!!!')
# 例外を指定（ちなみにJavaのように複数の例外を並べて発生する例外毎に処理を定義可）
try:
    val = 1 / 0
except ZeroDivisionError as e:
    print(e)
finally:
    print('finally')
# スタックトレースを得る
import sys
import traceback
try:
    val = 1 / 0
except Exception as e:
    exception_type, message, _trace = sys.exc_info()
    print(f'exception_type={exception_type.__name__}, message={message}')
    print(traceback.format_tb(_trace))
    print('================================')
    # 通常表示されるトレースを文字列にするなら以下の方法
    except_str = traceback.format_exc()
    print(except_str)
```

## 関数の定義

### 関数定義例
```python
def func(param1, param2='default', *args, **kwargs):
    # pythonはIndentでブロック管理する。（スペース４が標準）
    print(f'param1={param1}, param2={param2}')
    print(f'可変長引数={args}')
    print(f'名前付き引数={kwargs}')
    return 'result'
```
### 引数について

上述の関数を以下で実行した場合の結果例を示す。

```python
ret = func(1, 'abcde', True, foo='FOO', bar='BAR')
print(ret)
"""
param1=1, param2=abcde
可変長引数=(True,)
名前付き引数={'foo': 'FOO', 'bar': 'BAR'}
result
"""
```

## クラスの定義

### クラス定義例

```python
def SampleClass():
    """クラス定義の例です。
    pythonは１ファイル１クラスとしない方がよく見ますが、
    Java出身者が規約を決めたせいでそうなっているプロジェクトはあったりする。
    """

    # クラス変数(インスタンス化しなくてもアクセス可)
    class_variable = 10

    # コンストラクタ
    def __init__(self, param):
        """コンストラクタ。
        selfはインスタンス化された自身を指す
        第一引数がそうなるだけで"self"という名前でないといけないわけではないが、
        慣例的に"self"とする。
        """
        # インスタンス変数
        self.param = param
        # 疑似プライベートなインスタンス変数
        self.__pseudo_private = 'aaa'
        """↑が何で疑似というかというと、`インスタンス.__pseudo_private`では
        アクセスできないけど、`インスタンス._SampleClass__param`と書くと
        アクセスできるため。
        pythonにはアクセス修飾子は無いのでこんな使い分けをするのが慣例。
        （アンスコ１つをprivate的に扱われていることもあるが、アンスコ１つだと
        普通にアクセスできるのでアンスコ２つが原則）
        """
    
    # __init__の他にも__new__とか__call__というのも存在する。
    # https://qiita.com/FGtatsuro/items/49f907a809e53b874b18
    
    def method(self, param):
        """インスタンスメソッド
        """
        pass
    
    @classmethod
    def class_method(cls, param):
        """クラスメソッド
        第一引数がクラス自身の参照となる。
        """
        print('class method')

    @staticmethod
    def static_method(param):
        """スタティックメソッド
        """
        print('static method')
    
    # クラスメソッドとスタティックメソッドの使い分けは正直わからん。
    # （クラスメソッドの必要性を感じることがあまりない）

    def __str__(self):
        """javaのtoString()みたいなもの
        人が見たときにわかりやすい内容で返す。
        str(object) と組み込み関数 format(), print() によって呼ばれる
        """
        return self.param
    
    def __repr__(self):
        """こっちも似たようなものだが、こちらは
        再び元のオブジェクトに戻せる文字列で返す.
        組み込み関数`repr(オブジェクト)`の実行で呼ばれる
        __repr__しか定義していないと、str(オブジェクト)でもreprが呼ばれる
        """
        return self.__class__.__name__ + "(" + self.param + ")"
```

### 継承について

pythonでは多重継承が可能である。<br>
良し悪しは宗教論争になるので割愛するが、個人的にはケースバイケースで使ってもよいとは考える。

```python
def Parent1(object):
    def __init__(self):
        print('Parent1')

def Parent2(object):
    def __init__(self):
        print('Parent2')

def Child(Parent1, Parent2):
    """pythonではこんな感じに多重継承ができる。
    """
    def __init__(self):
        print('Child')
```

## 文字列加工の基本

文字列は配列なので配列の部分抽出の書き方を利用する。

```python
str = 'abcde'
# left
print(str[:3]) # => abc
# mid
print(str[2:4]) # => cd
# mid(末尾まで)
print(str[3:]) # => de
# right
print(str[-3:]) # => cde
# split
print(str.split('c')) # => ['ab', 'de']
# join(listのメソッドじゃなくて、strのメソッドなので注意)
print('@'.join(['abc', 'def', 'ghi'])) # => abc@def@ghi
```

## 文字列の書式変換

### C言語のprintf的なやつ

```python
# 左詰め/右詰め
print('|%5s|%-5s|' % ('A', 'B'))
# 整数
print('|%d|%05d|' % (123, 456))
# 浮動小数点
print('|%f|%5.3f|' % (123.456, 456.78))
# 他にもhex(16進数)とか8進数とかもあるが割愛
```

### format関数
```python
# format関数
print('{}={}'.format('key', 'value')) # => key=value
# 位置Index付き
print('{1}={0}'.format('key', 'value')) # => `value=key`と出力される
# python3.6からこんなことができるようになった（便利）
key = 'key'
value = 'value'
print(f'{key}={value}') # => key=vallue
```

## withステートメント

javaでいうtry-with-resourcesに近いもので自動でクローズしてくれる。<br>
基本的にクローズが必要なものはwithで書く。

```python
with open('memo.txt', 'r', encoding='utf-8') as f:
    for s_line in f:
        # print関数は末尾に改行が自動ではいる
        # またこの読み込み方法だと各行にも改行が入るので
        print(s_line, end='')
        # もしくは、末尾の改行をとる。
        # この書き方は改行コードがlfである前提でcrlfの場合は気を付ける）
        print(s_line[:-1])
```

## 内包表記

内包表記とは以下の例にあげるような書き方。
あるオブジェクトリストを特定条件で絞るとかをするときに１行で書けたりする。

```python
# 内包表記
list1 = [i for i in range(10)]
print(list1) # list(range(10))でも同じ。
# 条件付き内包表記（偶数だけ）
# リストから特定要素だけ抜くのを１行で書ける！！
list2 = [i for i in range(10) if i % 2 == 0]
print(list2)
```

## ラムダ(lambda)式

pythonもラムダ式使えます。<br>
ラムダ式とは無名関数を定義できる式みたい。（言葉の表現は怪しい…）<br>

```python
# 偶数/奇数の判定
get_odd_even = lambda x: 'even' if x % 2 == 0 else 'odd'
```

これは以下の関数定義と同等です。

```python
# ↑は以下と同等です。
def get_odd_even(x):
    if x % 2 == 0:
        return 'even'
    else:
        return 'odd'
```
上の例では、lambda式を`get_odd_even`に代入しているので無名というとちょっと違う気もするけど。。。

どんな時に使うというと、よく見るのはlistやdictを特定の条件でフィルタしたり、ソートしたりする場合。<br>
具体的には組み込み関数の`filter`と`sort`はフィルタ条件やソートルールの定義として引数に関数をとることができるが、そのときにわざわざ関数として定義する必要なないような場合にlambda式で定義したりする。

```python
int_list = [9, 3, 2, 7, 1, 6, 4, 5, 8]
# filter関数はpython3からイテレータを返すのでlist関数を適用する必要がある。
print(list(filter(lambda x:x % 2 == 0, int_list))) # [2, 4, 6, 8]
```

## コマンドライン引数

### sys.argvを利用した例。

```python:test.py
#!/usr/bin/env python
import sys
if __name__ == "__main__":
    for arg in sys.argv:
        print(arg)
```

./test.py A B C`と実行すると

```
/home/xxxx/python/studies/args.py
A
B
C
```
のようになる。（先頭はスクリプトのパス）

ただ、これだと引数の意味が分かりづらくなるので、実践的利用する場合は`argparse`を利用した方が望ましい。

### argparseの使い方。

スクリプトのコマンドライン引数を以下のように定義することで、自動的にチェックやヘルプなどを出力してくれる便利なパッケージモジュール。(標準pythonについてます）

```python:args.py
#!/usr/bin/env python
import argparse

class DataSizeAction(argparse.Action):
    """
    fooオプションが指定された場合のアクション定義クラス
    """
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        """
        独自チェックコンストラクタ
        Actionの使い方があってるかを実装するらしいが、例が思いつかないので割愛。。
        """
        super(DataSizeAction, self).__init__(option_strings, dest, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        """
        ここには当該オプションを受けた処理を記載する。
        例１）指定されたオプション値のバリデーションチェック
        例２）指定されたオプション値を加工する
        [参考]https://qiita.com/ronin_gw/items/af07ab62ea9e7213293d
        """
        try:
            if values.endswith('K'):
                data_size = int(values[:-1]) * 1024
            elif values.endswith('M'):
                data_size = int(values[:-1]) * 1024 * 1024
            elif values.endswith('G'):
                data_size = int(values[:-1]) * 1024 * 1024 * 1024
            else:
                data_size = int(values)

        except ValueError:
            parser.error(f'Invalid argument ({values})')

        setattr(namespace, self.dest, data_size)

# パーサーの生成
parser = argparse.ArgumentParser(description='このスクリプトの概要.')
# 必須の引数の追加（第一引数にハイフンを付けない）
parser.add_argument('required_param_1', help='必須パラメータ1')
parser.add_argument('required_param_2', help='必須パラメータ2')
# 任意の型定義付きパラメータ（名前にハイフン１つか２つで始まるようにつける。どちらか一方でも可）
parser.add_argument('-i', '--int_value', default=1, type=int, help='整数を指定')
parser.add_argument('-f', '--float_value', default=2.3, type=float, help='小数を指定')
parser.add_argument('-s', '--string_value', default='DEFAULT', type=str, help='文字列')
# Bool(True/False)が欲しい場合は、オプションの有無で以下のようにした方がよい。
parser.add_argument('-b', '--bool_value', action='store_true', help='このオプションを指定するとTrue')
# 複数指定
parser.add_argument('-l', '--list', nargs='*', type=int, help='選択肢')
# 選択肢
parser.add_argument('-c', '--color', choices=['red', 'blue', 'yellow'], help='信号の色')
# 独自のアクション定義
parser.add_argument('-d', '--data_size', action=DataSizeAction, help='指定された単位付きの値')

if __name__ == "__main__":
    # Namespaceというオブジェクトで返される。
    args = parser.parse_args()
    print(args)
    # Namespaceというオブジェクトで返される。
    print(f'args={args}')
    # こんな感じで各プロパティにアクセスできる
    print('====================================================')
    print(f'required_param_1={args.required_param_1}')
    print(f'required_param_2={args.required_param_2}')
    print(f'int_value={args.int_value}')
    print(f'float_value={args.float_value}')
    print(f'string_value={args.string_value}')
    print(f'bool_value={args.bool_value}')
    print(f'list={args.list}')
    print(f'color={args.color}')
    print('====================================================')
    # あんまり用途ないけど全パラメータを確認する方法として辞書化できる。
    args_dict = vars(args)
    print('====================================================')
    for key, value in args_dict.items():
        print(f'{key}={value}')
    print('====================================================')
```
１つ１つ説明すると長くなるので、これを<br>
`./args.py -i 1 -f 1.2 -s '!STRING!' -l 1 2 3 -c "red" -d 512M required_value_1 required_value_2`<br>
or<br>
`./args.py --int_value 1 --float_value 1.2 --string_value '!STRING!' --list_value 1 2 3 --color "red" --data_size 512M required_value_1 required_value_2`<br>
で実行した結果

```
Namespace(bool_value=False, color='red', data_size=536870912, float_value=1.2, int_value=1, list_value=[1, 2, 3], required_param_1='required_value_1', required_param_2='required_value_2', string_value='!STRING!')
args=Namespace(bool_value=False, color='red', data_size=536870912, float_value=1.2, int_value=1, list_value=[1, 2, 3], required_param_1='required_value_1', required_param_2='required_value_2', string_value='!STRING!')
====================================================
required_param_1=required_value_1
required_param_2=required_value_2
int_value=1
float_value=1.2
string_value=!STRING!
bool_value=False
list=[1, 2, 3]
color=red
====================================================
====================================================
required_param_1=required_value_1
required_param_2=required_value_2
int_value=1
float_value=1.2
string_value=!STRING!
bool_value=False
list_value=[1, 2, 3]
color=red
data_size=536870912
====================================================
```

あとは、以下のリンクを参照してください。
* [16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー](https://docs.python.jp/3/library/argparse.html)
* [Argparse チュートリアル](https://docs.python.org/ja/3/howto/argparse.html)