python

# Python

## 参考

- イテレータ・イテラブル・ジェネレータ
  - [Pythonにおけるイテレータとジェネレータについて学ぶ](https://zenn.dev/ganariya/articles/python-iterator-generator)
  - [図解「generator・native coroutine・with」 〜 関心やコードを分離する文法と、処理順序・構造](https://zenn.dev/339/articles/6832decc5ef1b4a0821b)

- async/await
  - [python の async/await 構文について調べる。](https://gist.github.com/nakamuray/13be3471ebed1e6845f2a3499e7e9575)

- その他

  - [「Pythonの実践」簡単速習‼【Webとネットワーク/応用③】](https://medical-science-labo.jp/python_advance03/)
  - [「Pythonの実践」簡単速習‼【並列化/応用⑤】](https://medical-science-labo.jp/python_advance05/)
  - [「Pythonの実践」簡単速習‼【非同期処理 asynico/応用⑫】
](https://medical-science-labo.jp/python_advance12/)

## 用語など

- イテレータ
- ジェレネータ
- コルーチン
- 並列化
  - マルチスレッド
  - マルチプロセス
- 非同期処理
  - コールバック
  - Promise
  - async/awaite

## まとめ

### イテレータ・イテラブル

- イテラブル(iterable)
  for 文で繰り返し処理ができるクラスのこと。
  実装としては、\_\_iter\_\_() or \_\_getitem\_\_() を実装しているクラス。
  - \_\_iter\_\_() はイテレータを意識したクラス
  - \_\_getitem\_\_() はリスト等を意識したクラス
　
    ```python
    lists = [1, 2, 3]
    for l in lists:
        print(l)

    # 書き換えると以下のように処理されている
    ite = [1, 2, 3].__iter__() # ite = iter([1, 2, 3])
    while True:
        try:
            l = ite.__next__() # l = next(ite)
        except StopIteration:
            break
        print(l)
    ```

    ```python
    class Sequence:
        def __getitem__(self, index):
            if 0 <= index <= 3:
                return index * 2
            else:
                raise IndexError

    seq = Sequence()
    print(seq[0])
    print(seq[1])
    print(seq[2])
    print(seq[3])

    for i in seq:
        print(i)
    ```
　
- イテレータ(iterator)
  実装としては、\_\_iter\_\_() or \_\_next\_\_() を実装しているクラス。
  イテラブルであるため、for文でも使用できる。また、iter(), next() を使ってイテレーションできる。

    ```python
    class Counter:
        def __init__(self, start, stop):
            self._counter = start
            self._stop = stop

        def __iter__(self):
            return self

        def __next__(self):
            if self._counter > self._stop:
                raise StopIteration()
            ret = self._counter
            self._counter += 1
            return ret
    
    c = Counter(start=1, stop=3)
    c = iter(c)
    print(next(c)) # 1
    print(next(c)) # 2
    print(next(c)) # 3
    print(next(c)) # StopIteration

    c = Counter(start=1, stop=3)
    for i in c:
        print(i)
    ```

### ジェネレータ

- ジェネレータ
  関数定義で、yield 式を使うことで定義される。
  通常の関数定義内で yield を使った場合は **ジェネレータ関数** となり、
  async def 関数定義内で使った場合は **コルーチン関数(非同期ジェネレータ関数)** となる。  
  [Python 言語リファレンス Yield 式](https://docs.python.org/ja/3/reference/expressions.html#yieldexpr)

  ※通常の関数と異なり、ジェネレータ関数がコールされた場合、
  すぐに関数内が実行されるわけではなく、ジェネレータイテレータオブジェクトを返す。
  その後にジェネレータメソッド(generator.__next__() or generator.send())がコールされて初めて関数が実行されることに注意。
    - `next(generator)` = `generator.send(None)` と考えていい？

    ```python
    def main():
        a = simple()        # 実行されずジェネレータを返すだけ
        print('first')
        x = a.send(None)    # ここで初めてジェネレータが（yieldまで）実行される
        print(x)            # third
        y = a.send('forth')
        print(y)            # fifth

    def simple():
        print('second')
        s = yield 'third'
        print(s)
        yield 'fifth'

    main()
    # first
    # second
    # third
    # forth
    # fifth
    ```

    ```python
    def echo(value=None):
        print("最初に 'next()' がコールされたときに実行が始まる.")
        try:
            while True:
                try:
                    value = (yield value)
                except Exception as e:
                    value = e
        finally:
            print("'close()' がコールされたときにクリーンアップを忘れないように")

    generator = echo(1)      # ジェネレータイテレータオブジェクトを返すだけ
    print(next(generator))   # 最初に 'next()' がコールされたときに実行が始まる. 
                             # 1
    print(next(generator))   # None
    print(generator.send(2)) # 2
    generator.throw(TypeError, "spam") # TypeError("spam")
    generator.close() # 'close()' がコールされたときにクリーンアップを忘れないように
    ```

- yield from

  from のあとにイテラブルを指定する。
  基本は、`yield from iterable` = `for item in iterable: yield item`

    ```python
    def g():
        for c in 'AB':
            yield c
        for i in range(1, 3):
            yield i
    print( list(g()) )  # ['A', 'B', 1, 2]

    def g2():
        yield from 'AB' 
        yield from range(1, 3)
    ```

  本来の目的は、「一番外側の呼び出し側」と「一番内側のジェネレータ」間で値の送受信や例外処理できるようにすること。

  ```python
    def delegator():
        while True:
            res = yield from subgenerator()
            print(res)

    def subgenerator():
        while True:
            recv = yield
            if recv is None:
                break
            print(recv)
        return 'fin.'

    g = delegator()  # ジェネレータイテレータオブジェクトを返すだけ
    next(g)
    g.send('A')      # A
    g.send('B')      # B
    g.send(None)     # fin.
  ```

### with

処理を終了する際に、必ず実行すべき処理があることが多い。
その場合、with を使って記述すると自動的に終了処理を行ってくれるようになる。
実装としては、\_\_enter\_\_() or \_\_exit\_\_() を実装しているクラス。

  ```python
  class WithClass:
      @classmethod
      def create(cls):
          return WithClass()

      def __init__(self):
          print("init")

      def hoge(self):
          print("hoge")

      def __enter__(self):
          print("enter")
          return self

      def __exit__(self, ex_type, ex_value, trace):
          print(f"exit: {ex_type} {ex_value} {trace}")
          # return True # 例外を無視することできる

  with WithClass() as w:   # クラスのインスタンス化＋処理開始
      w.hoge()             # 処理
                           # with を抜けた処理
  # init
  # enter
  # hoge
  # exit: None None None

  # 同じ動作をする
  with WithClass.create() as w:
      w.hoge()

  # 以下と等価
  manager = WithClass()
  enter   = type(manager).__enter__
  exit    = type(manager).__exit__
  value   = enter(manager)
  hit_except = False
  try:
      w = value
      w.hoge()
  except:
      hit_except = True
      if not exit(manager, *sys.exc_info());
          raise
  finally:
      if not hit_except:
          exit(namager, None, None, None)
  ```

### ソケット通信

  [A Complete Guide to Socket Programming in Python](https://www.datacamp.com/tutorial/a-complete-guide-to-socket-programming-in-python)

- ソケットは、抽象的なリモート接続管理のインターフェースである。
- ソケットは、ローカルPC内またはネットワーク越しのPC同士の異なるプロセス間（通常はサーバとクライアント）の情報伝達を可能にする。
- Pythonでは、ソケットを動作させるにはsocketライブラリを通して実現し、データ送受信のためのメソッドを持つソケットオブジェクトを提供している。
- ソケットプログラミングはデータサイエンスで役に立つアプリケーションを持つ。

    - Server

    ```python
    import socket
    
    server_ip   = "127.0.0.1"
    server_port = 8000
    
    def run_server():
        try:
    
            # ソケットのインスタンス化
            # socket.AF_INET : IPv4
            # socket.AF_INET6: IPv6
            # socket.SOCK_STREAM: TCP
            # socket.SOCK_DGRAM : UDP
            # socket.RAW_SOCKET : 低レベルプロトコル
            server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
            # ソケットをIPアドレスとポートに結び(bind)つける
            server.bind((server_ip, server_port))
    
            # クライアントを待ち受ける
            # backlog: acceptしていない接続をキューにいれる最大数。0 = 1クライアントだけ受け入れる
            #  省略するとOSのデフォルト値が使われる(/proc/sys/net/core/somaxconn)
            server.listen(0)
            print(f"Listening on {server_ip}:{server_port}")
    
            # 接続を受け入れる（クライアントアクセスがあるまで処理が止まる）
            # 通常処理ではaccept() 後はクライアント接続がブロックされるので１クライアントしか処理できない
            # client_socket : 接続したクライアントのソケット
            # client_address: 接続したクライアントのIP情報
            client_socket, client_address = server.accept()
            print(f"Accepted connection from {client_address[0]}:{client_address[1]}")
    
            # 通信処理
            while True:
                # byte で取得される
                request = client_socket.recv(1024)
                request = request.decode("utf-8")
                
                if request.lower() == "close":
                    client_socket.send("closed".encode("utf-8"))
                    break
                
                print(f"Recieve: {request}")
                response = "accepted".encode("utf-8")
                client_socket.send(response)
    
        except Exception as e:
            print("Error occurred: {e}")
        finally:
            # 切断処理。システムリソースを解放する。
            client_socket.close()
            print("Connection to client closed")
    
        # サーバを停止
        server.close()
    
    run_server()
    ```

    - Client

    ```python
    import socket
    
    server_ip = "127.0.0.1"
    server_port = 8000
    
    def run_client():
        # ソケットのインスタンス化
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # サーバに接続
        client.connect((server_ip, server_port))
    
        try:
            # 通信処理
            while True:
                # メッセージ入力し、サーバへ送信
                msg = input("Enter message: ")
                client.send(msg.encode("utf-8")[:1024])
                
                response = client.recv(1024)
                response = response.decode("utf-8")
                if response.lower() == "closed":
                    break
                
                print(f"Recieved: {response}")
    
        except Exception as e:
            print("Error occurred: {e}")
        finally:
            client.close()
            print("Connection to server closed")
    
    run_client()
    ```

### 並列化

  [その並列処理待った！　「Python 並列処理」でググったあなたに捧ぐasync, threading, multiprocessingのざっくりとした説明](https://qiita.com/Toyo_m/items/992b0dcf765ad3082d0b)

  [Pythonをとりまく並行/非同期の話し](https://tell-k.github.io/pyconjp2017/#13)

`並列処理` => 本当に複数の処理を同時に実行する
`並行処理` => 複数の処理を効率よく切り替えながら`あたかも`同時に実行する


### async/await

- [Pythonにasyncioってあるけどよく知らなかったので調べた](https://zenn.dev/knowhere_imai/articles/ba850780152b01)
- [Python標準ライブラリ > コルーチンと Task](https://docs.python.org/ja/3/library/asyncio-task.html#)

- Awaitable
  await 式の中で使うことができるもの
  - コルーチン
  - Task
  - Future

### future