# Python的一些套件(black、isort、pylint) + pre-commit
[TOC]
### 遇到的問題
- 在寫python的時候時常都會有這些問題
    1. 這個function輸入/出參數那麼多,我該寫成一行讓整體程式碼比較短,還是換行讓參數更簡潔哩?
    2. 我import了一堆東西,有來自內建的、第三方的、本地的,到底怎麼排比較好啊?
    3. 我命名的參數、變數會不會有什麼問題啊?
### 解決方案
1. 到底什麼時候要換行,你搞得我好亂啊!!!
    - 使用`formatter`相關套件來解決這類的問題,像是`換行位置`、`注釋的格式`、`注釋掉的程式碼`或`紀錄檔格式中的錯誤做法之類`的問題
    - formatter御三家
        - black (⭐️ 30.4k)
        - yapf (⭐️ 12.9k)
        - autopep8 (⭐️ 4.2k)
    - [black使用教學](https://sean22492249.medium.com/%E4%BD%BF%E7%94%A8-black-formatter-%E5%9C%A8-vscode-4217b8ceb6f2)
    - Example
        - 原本
        ```python
        # 1
        def discard_card(self, chosen_player: "Player" = None, discarded_card: Card = None, with_card: "Card" = None):
            pass
        # 2
        def handle_card_action(self,
                               turn_player: "Player",
                               discarded_card: "Card",
                               action: Union[GuessCard, ToSomeoneCard, None]) -> None:
            pass
        # 3
        def to_dict(self):
            return dict(game_id=self.id, players=[x.to_dict() for x in self.players],
                        rounds=[x.to_dict() for x in self.rounds])
        ```
        - `black xxx.py`後
        ```python
        # 1
        def discard_card(
            self,
            chosen_player: "Player" = None,
            discarded_card: Card = None,
            with_card: "Card" = None,
        ):
            pass
        # 2
        def handle_card_action(
            self,
            turn_player: "Player",
            discarded_card: "Card",
            action: Union[GuessCard, ToSomeoneCard, None],
        ) -> None:
            pass
        # 3
        def to_dict(self):
            return dict(
                game_id=self.id,
                players=[x.to_dict() for x in self.players],
                rounds=[x.to_dict() for x in self.rounds],
            )
        ```
2. 解決一堆import問題的套件
    - 使用`isort`套件來解決這類的問題,將import分為「內建」、「第三方」、「本地」套件排序
    - [isort使用教學](https://blog.kyomind.tw/isort/)
    - Example
        - 原本
        ```python
        # 本地
        from love_letter.models.cards import Card, Deck, PriestCard, find_card_by_name
        from love_letter.models.exceptions import GameException
        from love_letter.web.dto import GuessCard, ToSomeoneCard
        # 第三方
        from copy import deepcopy
        from dataclasses import dataclass
        from typing import List, Union
        
        # 內建
        import secrets
        ```
        - `isort xx.py`後
        ```python
        # 內建
        import secrets
        
        # 第三方
        from copy import deepcopy
        from dataclasses import dataclass
        from typing import List, Union
        
        # 本地
        from love_letter.models.cards import Card, Deck, PriestCard, find_card_by_name
        from love_letter.models.exceptions import GameException
        from love_letter.web.dto import GuessCard, ToSomeoneCard
        ```
3. 命名的參數、變數有什麼問題啊?
    - 使用`Linter`套件來解決這類的問題,檢查你的`code`,告訴你哪裡寫的不好,應該要改進。強大的PEP8判斷,讓你快速學習coding style
    - Linter有以下兩款
        - [flake8(⭐️ 2.5k)](https://github.com/PyCQA/flake8)
        - [pylint(⭐️ 4.4k)](https://github.com/PyCQA/pylint)
    - pycharm有plugin可以使用,可以在`.pylintrc`中的`disable`設定要關閉的格式驗證(`missing-module-docstring`、`missing-class-docstring`)
    - [Pycharm中pylint使用教學](https://cloud.tencent.com/developer/article/1486578)
    - Example
        - 原本
        ```python
        def decorate_with_card_usage(self, raw_result):
            for round in raw_result['rounds']:
                pass
        ```
        - `pylint xx.py`後,跟你說你重複定義了內建的方法`round`,換一個命名就能解決此問題
        ```sh
        xx.py:2:8: W0622: Redefining built-in 'round' (redefined-builtin)
        xx.py:2:8: W0612: Unused variable 'round' (unused-variable)
        ```
### 上面介紹了一堆套件,拿到我每次commit、push都要自己每個套件都跑過一次嗎? 應該有個東西可以幫忙整理吧?
- `pre-commit`: Git pre-commit hooks 工具,能夠在執行`git commit`時幫你進行你設定的檢查,必須另外寫`.pre-commit-config.yaml`來指定上面說的三種套件
- `.pre-commit-coifig.yaml`
```yaml
repos:
  - repo: https://github.com/psf/black
    rev: 22.10.0
    hooks:
      - id: black
        name: black
  - repo: https://github.com/PyCQA/isort
    rev: 5.10.1
    hooks:
      - id: isort
        name: isort
  - repo: https://github.com/PyCQA/pylint
    rev: v2.15.5
    hooks:
      - id: pylint
        name: pylint
        entry: pylint
        language: system
        types: [python]
        args:
          [
            "-rn", # Only display messages
            "-sn", # Don't display the score
          ]
```
- [pre-commit使用教學](https://myapollo.com.tw/zh-tw/pre-commit-the-best-friend-before-commit/)
### 總結
- 透過這些套件讓我們在開發python project的時候可以更加安心的開發,不用擔心`排版`、`語法錯誤`、`不符合PEP8`等事情。最後再透過`pre-commit`來幫我們統一整合在`git commit`時觸發,讓我們push上去的code都是經過這些套件驗證過的。
- 後續:可以在github action加入這些套件的CI測試,防止有人沒按照規矩來push