pythonのお勉強

# はじめに
pythonのおべんきょ用にアウトプットするです。
選出基準は私が面白いと思ったか否かです。順番とかないです()
ライブラリ関係は今回は飛ばします。

## 基本

### ・型
:ballot_box_with_check: pythonには以下の型がある。
| 型 | 説明 |
|:-----------|:------------|
| str   | 文字列型      |
| int   | int型        |
| list  | リスト型(配列)|
| dict  | 辞書型(jsでいうオブジェクトみたいな)          |
| bool  | ブール型     |
| float | 浮動小数点型 |
| tuple | タプル型     |

```python
from typing import List, Dict, Tuple

tmp_str: str = "hoge"
tmp_int: int = 1
tmp_list: List[str] = ["hoge", "huga"]
tmp_dict: Dict[str, str] = {
    "hoge": "hoge1",
    "fuga": "fuga1",
}
tmp_bool: bool = False
tmp_float: float = 1.41421356237
tmp_tuple: Tuple[int, int, str] = (0, 1, "2")
```

:::note warn 
tupleとlistの違い

要素数が決まっているか否かというのもそうだが、
・tuple -> イミュータブル(変更不可)
・list ->　ミュータブル(変更可)
という違いがある。
```python
tmp_list[0] = "aaa" # -> OK
tmp_tuple[0] = 3    # -> NG: TypeError: 'tuple' object does not support item assignment
```
:::

### ・型アノテーション
:ballot_box_with_check: 変数が何の型か明示的に示すときに型アノテーションというものを使う。
```python
tmp: string = "hoge"
#変数: 型名
```

### ・繰り返し文
:ballot_box_with_check: `for` や `while`がある。

**・for**
```python
# for 変数 in 繰り返し条件:   みたいな書き方が通例

#1. 0からNまで繰り返し
for i in range(5):
  print(i, end=', ') # -> 0, 1, 2, 3, 4, 

#インデックスが要らない場合
for _ in range(5):
  print("hello world", end='!, ') # -> hello world!, hello world!, hello world!, hello world!, hello world!, 
```
```python
#2. AからBまで繰り返し
for i in range(2, 5):
  print(i, end=', ') # -> 2, 3, 4, 
```

```python
#3. AからBまでN回飛ばしで繰り返し
#range(start, stop, step)

for i in range(2, 10, 2):
  print(i, end=', ') # -> 2, 4, 6, 8,

# 以下のようにすれば逆順で繰り返しも可
for i in range(10, 5, -1):
  print(i, end=', ') # -> 10, 9, 8, 7, 6,
```

```python
#4. オブジェクトを繰り返し処理
list_var = ["apple", "banana", "grape", "peach"]
for fruit in list_var:
  print(fruit, end=', ') # -> apple, banana, grape, peach

tuple_var = ("neko", True, 123, 0.222)
for i in tuple_var:
  print(i, end=', ') # -> neko, True, 123, 0.222, 
```
```python
#5. オブジェクトをインデックス付きで繰り返し処理
list_var = ["apple", "banana", "grape", "peach"]
for index, fruit in enumerate(list_var):
  print(index, fruit, end=', ') # -> 0 apple, 1 banana, 2 grape, 3 peach, 
```

```python
#6. dictからkeyのみ取得
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for key in dict_var.keys():
  print(key, end=', ') # -> neko, inu, とり, 

for index, key in enumerate(dict_var.keys()):
  print(index, key, end=', ') # -> 0 neko, 1 inu, 2 とり,
```

```python 
#7. dictからvalueのみ取得
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for value in dict_var.values():
  print(value, end=', ') # -> にゃー, わん, ぴよぴよ, 
```

```python
#8. dictからkey, valueのセットを受け取る
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for k,v in dict_var.items():
  print(k, v, end=', ') # -> neko にゃー, inu わん, とり ぴよぴよ, 

for index, (k,v) in enumerate(dict_var.items()):
  print(index, k, v, end=', ') # -> 0 neko にゃー, 1 inu わん, 2 とり ぴよぴよ, 

```

```python
#9. リストの特定のものを処理したいとき
# rangeと同じく、list[start:stop:step]と書ける。
list_var = ["python", "c", "objective-c", "java", "typescript"]

for list in list_var[1:3]:
  print(list, end=', ') # ->c, objective-c,

for list in list_var[2:len(list_var):2]:
  print(list, end=', ') # -> objective-c, typescript, 

for list in list_var[::-1]:
  print(list, end=', ') # -> typescript, java, objective-c, c, python, 
```

### ・if
```python
if(条件):
  処理(正)
elif (条件):
  処理(偽)
else:
  処理(その他)
```
```python
#三項演算子 真の値 if 条件 else 偽の値
a = 1
b = 2 if a == 1 else 0 
# -> b = 2
```

・比較演算子
| 演算子 | 説明 |
|:-----------|:------------|
| ==   | 等値      |
| !=   | 否定        |
| a < b  | aはbより小さい|
| a <= b | aはb以下    |
| a > b | aはbより大きい |
| a >= b | aはb以上 |
| is | 等値   |
| not | 否定 |
| a and b | aもbも真 |
| a or b | aかbが真 |
| a in b | aがbに含まれる |

### ・ウォルラス(セイウチ)演算子
```python
import random

if (num := random.randint(1,10) > 5):
    print("win")
else:
    print("lose")

# -> lose
```

:ballot_box_with_check: `:=`のことをウォルラス演算子という。代入した結果をそのまま式として使える。
<sup>便利だけど可読性が下がりそうなので節度が要りそう...</sup>



## 関数系

### ・アロー演算子(関数アノテーション)
```python
def hogeFn() -> str:
    return "hoge";
    
print(hogeFn());

# -> hoge
```

:::note warn
関数アノテーションとは、関数の戻り値の型を明示的に示すことができる機能。
ただ、別に強制力はなく、戻り値の型が違っていても動くらしい。
:::
参考

https://note.nkmk.me/python-function-annotations-typing/

### ・デコレーター(関数)
```python
#例
import functools
import os

def decorator(fn):
  def helper(args, kwds):
    print("helper\nargs: ", args, "\nkwds: ",kwds, end="\n\n");
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
    helper(args, kwds)
    response = fn.__name__ + 'を開始しました。\n'
    result = fn(*args, **kwds)
    response += 'result: ' + str(result) + os.linesep
    response += fn.__name__ + 'を終了しました。\n'
    return response

  return wrapper

@decorator
def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())
print(test_fn.__name__)


"""
↓出力結果

helper
args:  ((), {}) 
kwds:  {}

test_fnを開始しました。
result: 6
test_fnを終了しました。

test_fn
"""
```
:::note warn
デコレーターとは？
デコレーター = 関数をラップして返す関数。

ある関数を呼び出したときに、`@関数名`を書くと、デコレーターになり、デコレーターをつけた関数が実行される前にデコレーターが実行される。
:::
上の例で説明すると、

① この部分でtest_fnが実行される前にdecoratorが実行される。
```python
@decorator
def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())
```

② デコレーター内部では`wrapper`関数によって、呼び出し元の関数を実行し、その結果を加工して返している。
```python
# デコレーター関数
def decorator(fn):
  def helper(args, kwds):
    print("helper\nargs: ", args, "\nkwds: ",kwds, end="\n\n");
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
    helper(args, kwds)
    response = fn.__name__ + 'を開始しました。\n'
    result = fn(*args, **kwds)
    response += 'result: ' + str(result) + os.linesep
    response += fn.__name__ + 'を終了しました。\n'
    return response

  return wrapper
```
:::note warn 
<details><summary>@functool.wrapsについて</summary>
この記述は、呼び出し元の関数のメタデータを保持するための記述。

メタデータとは❓
　 => `関数の名前`だったり、`関数の型定義`だったり、メタ的な情報のこと。これがあることでデバッグに役立つ。

❌`@functool.wraps`を書かない
```python
# デコレーター関数
def decorator(fn):
  ~
# @functools.wraps(fn)
  def wrapper(*args, **kwds):
   ~
  return wrapper

print(test_fn.__name__) # => wrapper
```
このように、test_fnを呼んでいるのに、wrapper関数の情報で上書きされてしまう。これは、デコレータの中で実行関数がwarpperに上書きされてしまっているから。

✅ `@functools.wraps`を使いましょう。

```python
# デコレーター関数
def decorator(fn):
  ~
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
   ~
  return wrapper

print(test_fn.__name__) # => test_fn
```
</details>
:::

:::note warn
<details><summary>wrapper(*args, **kwds)について</summary>

*argsとは❓
   => 呼び出し元の関数の、引数のこと。
ちなみに、pythonでは変数の前に"\*"をつけると、`可変長引数`になる。

**kwdsとは❓
   => 呼び出し元の関数にkey-valueのdictがあったときに、これを受け取る。
pythonでは、変数の前に"\*\*"をつけると、`dict`になる。

例えば
```python
@decorator
def test_fn(name: str, age: int, kind:str = None):
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn("neko", 2, kind="cat"))

"""
こうすると、出力が以下のようになる。

helper
args:  ('neko', 2) 
kwds:  {'kind': 'cat'}
"""
```
</details>
:::
<br/>

③ 最後に、呼び出し元のtest_fnの中身が実行される。
```python
def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())
```

💡活用例
・クエリ前にキャッシュを確認する関数をデコレーターにする。
```python

#キャッシュがあるかを確認し、あればキャッシュを、なければ実行関数を返す。
def check_cache(fn):
  #cache確認関数
  def check_cache_fn()
  
  def wrapper(*args, **kwds):
    cache = check_cache_fn()
    if (isinstance(cache, CacheClass)):
      return cache
    else:
      return fn(args, kwds)
  return wrapper

@check_cahce
def getBook(id: str):
  # ~
  # クエリ処理
  # ~

getBook("1")
```

## クラス系

### ・基本
```python
class TestClass():
  def __init__(self, hoge):
    self.hoge = hoge

  def show_hoge(self):
    print(self.hoge)

test_class_instance = TestClass("hoge")
test_class_instance.show_hoge() # => hoge
```

:ballot_box_with_check: classを書くときは、`__init__`メソッドと`self`を書く。

・`__init__`とは❓
インスタンス化するとき、必ず最初に呼ばれるメソッドのこと。

・`self`とは
インスタンス化したとき、生成されるオブジェクトそのもの。
だからself.methodで参照できるんすね😙

### ・withメソッド
```python
import datetime

class AuthenticationError(Exception):
    pass
    
class TestClass():
  def __init__(self, token):
    self.token = token

  def __enter__(self):
    print("処理開始")
    expired = self.token.accessToken.expired
    today = datetime.datetime.today()
    if (expired < today):
      raise AuthenticationError("token has expired")
    else: 
      # ~
      # connection処理など
      # ~
      
  def __exit__(self, exc_type, exc_value, traceback):
    # ~
    # コネクション切断処理など
    # ~

  def getBook(self):
    # ~
    # クエリ処理など
    # ~

def getBooks():
  try:
    with TestClass(token) as test:
      test.getBook()
  except:
    #例外処理

getBooks()
```

:ballot_box_with_check: withメソッドは、コードの実行前後に特定の処理を挟みたいときに便利なメソッド。

① class側の定義
```python
class TestClass():
  ~
  
  def __enter__(self):
    print("処理開始")
    expired = self.token.accessToken.expired
    today = datetime.datetime.today()
    if (expired < today):
      raise AuthenticationError("token has expired")
    else: 
      # ~
      # connection処理など
      # ~
      
  def __exit__(self, exc_type, exc_value, traceback):
    # ~
    # コネクション切断処理など
    # ~

  ~
```

withメソッドを使うときには、classに`__enter__`メソッドと、`__exit__`メソッドが必要。

・`__enter__` => withでラップしたコードが実行される前に走るメソッド。

・`__exit__` => withでラップしたコードが実行された後に走るメソッド。

② withメソッド
```python
with TestClass(token) as test:
      test.getBook()
```
・withメソッドを使って、instance化すると、`__enter__`メソッドと`__exit__`メソッドを挟んで処理を実行できる。

=> DBとの接続準備だったり、切断処理だったりを簡潔に書くことができ、大変便利💮

### ・デコレーター(クラス)
二度目のデコレーターの登場。

```python
from typing import Union

book_list = {
    "1": {"タイトル": "ノルウェイの森", "著者": "村上春樹", "ジャンル": "小説", "出版年": 1987},
    "2": {"タイトル": "コンビニ人間", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2016},
    "3": {"タイトル": "アンドロイドは電気羊の夢を見るか?", "著者": "フィリップ・K・ディック", "ジャンル": "SF", "出版年": 1968},
    "4": {"タイトル": "嫌われる勇気", "著者": "岸見一郎", "ジャンル": "哲学", "出版年": 2013},
    "5": {"タイトル": "アリスのままで", "著者": "リリ・アール", "ジャンル": "小説", "出版年": 2007},
    "6": {"タイトル": "エンド・オブ・エタニティ", "著者": "アイザック・アシモフ", "ジャンル": "SF", "出版年": 1955},
    "7": {"タイトル": "ハリー・ポッターと賢者の石", "著者": "J.K.ローリング", "ジャンル": "ファンタジー", "出版年": 1997},
    "8": {"タイトル": "地獄の季節", "著者": "芥川龍之介", "ジャンル": "小説", "出版年": 1927},
    "9": {"タイトル": "コンビニ人間のその後", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2021},
    "10": {"タイトル": "1984年", "著者": "ジョージ・オーウェル", "ジャンル": "ディストピア", "出版年": 1949},
    "11": {"タイトル": "月曜日のたわわ", "著者": "柴田よしき", "ジャンル": "エッセイ", "出版年": 2015},
    "12": {"タイトル": "風と共に去りぬ", "著者": "マージェリ・シャーウッド", "ジャンル": "小説", "出版年": 1936},
    "13": {"タイトル": "人間失格", "著者": "太宰治", "ジャンル": "小説", "出版年": 1948},
    "14": {"タイトル": "未来のぼくら", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2019},
    "15": {"タイトル": "アントワネット", "著者": "ステファニー・デュモン", "ジャンル": "歴史小説", "出版年": 2010},
    "16": {"タイトル": "鴨川ホルモー", "著者": "万城目学", "ジャンル": "小説", "出版年": 2007},
    "17": {"タイトル": "永遠の0", "著者": "百田尚樹", "ジャンル": "小説", "出版年": 2006},
    "18": {"タイトル": "イノセンス", "著者": "乙一", "ジャンル": "小説", "出版年": 2001},
    "19": {"タイトル": "ノルウェーの森 2", "著者": "村上春樹", "ジャンル": "小説", "出版年": 1990},
    "20": {"タイトル": "カラフル", "著者": "森絵都", "ジャンル": "小説", "出版年": 2008}
}

class CacheDecorator:
    def __init__(self, func):
        self.func = func
        self.cache = {}
    
    def __call__(self, *args):
        if args in self.cache:
            print("Returning cached result. id:", args[0])
            return self.cache[args] 
        result = self.func(*args)
        self.cache[args] = result
        return result

@CacheDecorator
def getBook(id:str) -> Union[dict, None]:
  for book_id, value in book_list.items():
    if book_id == id:
      return value
  return None

book1 = getBook("1")
book2 = getBook("2")
book3 = getBook("1")

print(f"{book1}\n{book2}\n{book3}")

"""
出力結果

Returning cached result. id: 1
{'タイトル': 'ノルウェイの森', '著者': '村上春樹', 'ジャンル': '小説', '出版年': 1987}
{'タイトル': 'コンビニ人間', '著者': '村田沙耶香', 'ジャンル': '小説', '出版年': 2016}
{'タイトル': 'ノルウェイの森', '著者': '村上春樹', 'ジャンル': '小説', '出版年': 1987}
"""
```

:ballot_box_with_check: ~~クラスのデコレーターでは`__call__`メソッドが関数の前に呼ばれる。~~
~~クラスでデコレーターを作成すると、`__call__`というメソッドを呼ぶことができる。~~

・12/23追記
**これは間違い。** `__call__`が呼ばれるタイミングは、クラスをインスタンス化して、関数として使用した場合。
すなわち以下のような場合になる。
```python
class TestDec:
  def __init__(self):
    ~~~

  def __call__(self):
    print('called')

@TestDec
def my_function():
    print("Hello!")

my_function() # -> called が呼ばれる。

```

逆に、最初から別の関数を返すように明示すれば、__call__は呼ばれない。
```python

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def helper(self, *args, **kwargs):
        print("Helper called!")
        return self.func(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("This won't be called")

@MyDecorator
def my_function():
    print("Hello!")

# helperを明示的に指定
my_function = my_function.helper  # helper関数を直接使う

my_function()  # __call__ は呼ばれず helper が呼ばれる

```
今回の例では、
`__init__`(CacheDecoratorクラス) -> `__call__`(CacheDecoratorクラス) -> `getBook`
という順で実行される。

`*args`などの挙動は変わらないので、今回みたいにクエリ前にcacheを保存しておいて、後で取り出すみたいな書き方が出来る。

## その他

### ・f_string
フォーマット済み文字列リテラル。文字列中に変数や関数を埋め込むことができる。
```python
name = "タマ"
print(f"吾輩は猫である。名前は{name}") # -> 吾輩は猫である。名前はタマ
```

:ballot_box_with_check: f_stringを使うときは文字列前に`f`を書く。変数は`{}`の中に書く。

### ・try, expect, raise, assert

・try
  => try-expect句の開始の文言。
・expect
  => try-expect句のエラー時の文言。jsとかでいうcatch。
・raise
  => エラーを発生させるための文言。jsとかでいうthrow。
・assert
  => `aseert 条件式, エラーの時の文言`という形式で書き、Trueの時は特に何も起こらないが、Falseの時はraiseと同様、エラーを投げる。

```python
try:
  ~
  処理
  ~
  raise NameError('変数名が異常です。') #エラーを投げる
  assert 1 == 2, "間違い" #Falsyな時だけエラーを投げる。
expect Exception as e:  #エラーが起こったらここに入る。
  print("an Error occured. Error:", e)
```

# 終わり
デコレーターにとても可能性を感じています。
まだまだ慣れませんが、今後も勉強:innocent: