🐍

Python + VSCode の環境構築 20240604

2024/06/04に公開
2

作業メモ。モダン Python 速習。

AI 周りのツールを動かしていたら TypeScript だけでやるには無理が出てきたので、久しぶりに Python の環境構築をする。

具体的には TestGen LLM を動かしたい。

https://www.freecodecamp.org/news/automated-unit-testing-with-testgen-llm-and-cover-agent

Python はたまに触るけど、基本 2.x 時代の知識しかない。

基本的にこの記事を読みながら、細かいアレンジをしている。

https://zenn.dev/koki_algebra/articles/cd3341bcba9272

追記

  • rye が ruff と pytest を同梱してるので rye fmt, rye check, rye test で良かった
  • uvicorn を叩くより、 fastapi-cli を使って起動したほうが良さそうので変更

基本方針: Rye に全部任せる

良く出来てると噂に聞いたので、 rye に任せる。

自分が Python が苦手な点は pip を下手に使うと環境が汚れていく点で、基本的に rye で閉じて管理させる。システムの Python には指一本触れさせない。

https://rye.astral.sh/guide/installation/

Mac とWindows (WSL) で同じものをセットアップしたが、とくに環境差分はなかった。

プロジェクトのセットアップ

rye init でプロジェクトを作って、そこで作業をする。

$ rye init study-py
$ cd study-py
$ rye pin 3.12 # バージョン固定
$ rye sync

フォーマッタ: ruff

Python は無設定だとインデントで苦労するので、フォーマッタを入れておく。

$ rye add ruff -d
## 試しに実行してみる
$ rye run ruff check

グローバルに入れなくても .venv 以下の実体に対してパスが刺さって動いてくれた。

基本的に設定は書かないことにする。郷に従う。

ruff の vscode 拡張をいれる。

https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff

onSave でフォーマットするようにしておく。

.vscode/settings.json
{
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true
  }
}

追記 rye は ruff を同梱してる

$ rye check
$ rye fmt

型と TypeChecker の使い方を確認

最近の Python には型があるので、TypeChecker を入れる。

実装が乱立しているが、長いものに巻かれたほうが良さそうなので、一番ユーザー層が厚そうな mypy を入れておく。

$ rye add mypy -d

わざと間違ってるコードを書いてチェックさせてみる。

def add(a: int, b: str) -> int:
    return a + b
$ rye run mypy src
src/play.py:2: error: Unsupported operand types for + ("int" and "str")  [operator]
Found 1 error in 1 file (checked 4 source files)

これを vscode から見るために拡張をいれる。

https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker

インストールすると vscode で型エラーが見えるようになった。

(pyright 作ってる MS が、 mypy の vscode 拡張を作ってるのが、ちょっと不思議)

mypy, pyright, pylance, pydantic の比較は後でやる。自分はたぶん pyright が好きそう。

ライブラリに対する補完を確認

サードパーティのコードをチェックする能力があるか確認する。

$ rye add numpy
import numpy as np

def run():
    x = np.array([1, 2, 3])
    y = np.array([4, 5, 6])
    print(np.add(x, y))

np. の時点で vscode の補完が効くようになった。

ライブラリの補完が効くのはだいぶ体験いい。5年ぐらい前に ゼロから作るDeep Learning を写経したときのだが、そのときにほしかった。

https://www.oreilly.co.jp/books/9784873117584/

うろ覚えになってるので、もう一回チャレンジするのもいいかも。

dataclass

というものがあるのは知ってたので、一応使っておく。

import dataclasses

@dataclasses.dataclass
class Person:
    age: int
    name: str = 'Alice'

if __name__ == '__main__':
    p1 = Person(0, 'Bob')
    print(p1.age)
    print(p1.name)

レコード型みたいな感じで使う感じっぽい。

テストランナーの使い方を確認

pytest の設定をする

$ rye add pytest -d

これを参考に

https://rinatz.github.io/python-book/ch08-02-pytest/

def is_prime(n: int) -> bool:
    if n <= 1:
        return False

    if n == 2:
        return True

    if n % 2 == 0:
        return False

    i = 3

    while i * i <= n:
        if n % i == 0:
            return False

        i += 2

    return True

def test_is_prime():
    assert not is_prime(1)
    assert is_prime(2)
    assert is_prime(3)
    assert not is_prime(4)
    assert is_prime(5)
    assert not is_prime(6)
    assert is_prime(7)
    assert not is_prime(8)
    assert not is_prime(9)
    assert not is_prime(10)

実行する

$ rye run pytest      
============================= test session starts ==============================
platform darwin -- Python 3.12.3, pytest-8.2.1, pluggy-1.5.0
rootdir: /Users/kotaro.chikuba/sandbox/study-py
configfile: pyproject.toml
plugins: anyio-4.4.0
collected 1 item                                                               

src/foo_test.py .                                                        [100%]

============================== 1 passed in 0.01s ==============================

追記: rye が pytest を同梱している

ので、 rye test のみで ok

$ rye test

fastapi でサーバーを立てる

サーバーを立ててみる。

$ rye add fastapi
$ rye add uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"greeting": "Hello world"}

サーバーを実行

$ rye run uvicorn main:app --reload

追記: uvicorn を叩くより fastapi-cli のが良さそう

こっちのが明示的

$ rye add fastapi-cli fastapi uvicorn 
$ rye run fastapi dev main.py

fastapi + pydantic

fastapi の入出力に pydantic で JSONSchema のランタイムチェックを入れる。
使った感じ、 zod に近い印象。

https://docs.pydantic.dev/latest/

https://fastapi.tiangolo.com/ja/tutorial/body/

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List


class UserInput(BaseModel):
    name: str
    age: int


class User(BaseModel):
    id: int
    name: str
    age: int


app = FastAPI()


@app.post("/users/", response_model=User)
async def create_user(input: UserInput):
    return User(name=input.name, age=input.age, id=1)


@app.get("/users/", response_model=List[User])
async def users() -> List[User]:
    user_saved = User(name="John", age=30, id=1)
    return [user_saved]


@app.get("/")
async def root():
    return {"greeting": "Hello world"}

昔 flask 使ったことがあったので、特に違和感はない。

一応推奨に従って typeCheckingMode を strict にしておく。

{
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true
  },
  "python.analysis.typeCheckingMode": "strict"
}

静的な pydantic のチェックはないっぽい?
自分で型を書かないといけないっぽい。

FastAPI のスキーマから TypeScript の型定義を生成する。

自分は普段遣いが TypeScript なので、OpenAPI の spec を経由して型をつけて叩きやすくしておく。

これを参考にしている。

https://zenn.dev/horitaka/articles/fastapi-openapi-typescript

fastapi のエントリポイントが main.py だとして、それを利用して jsonschema を生成させる spec.py を用意する。

from main import app
import json

with open("spec.json", "w") as f:
    api_spec = app.openapi()
    f.write(json.dumps(api_spec))

これを実行しつつ、 deno のスクリプトで TypeScript 用の型定義を生成する。

$ rye run python spec.py
$ deno run -A npm:openapi-typescript spec.json -o spec.d.ts

spec.d.ts が生成できた。これを使って TypeScript から呼ぶクライアントの型を自動で導出する。
単純なクライアントを使いたいので、 fetch のラッパーを使う

https://www.npmjs.com/package/openapi-typescript-fetch

(Deno で書くが、別に npm にやることは変わらない)

import type { paths } from "./spec.d.ts";
import { Fetcher } from 'npm:openapi-typescript-fetch@2.0.0';

const fetcher = Fetcher.for<paths>()
fetcher.configure({
  baseUrl: 'http://localhost:8000',
})
const getUsers = fetcher.path('/users/').method('get').create();
const res = await getUsers({});
console.log(res.data);
/*
[ { id: 1, name: "John", age: 30 } ]
*/

ちゃんとレスポンスに型が付いて使いやすくなった。

追記: spec.json の生成をワンライナー化

別にセットアップ用のスクリプトを用意するほどではない気がしたので、ワンライナー化した。

$ rye run python -c "import main;import json;print(json.dumps(main.app.openapi()))" > spec.json
$ deno run -A npm:openapi-typescript spec.json -o spec.d.ts

transformers

せっかくなのでモデルを実行したい。transformers から軽量そうな感情分析モデルを使ってみる。

$ rye add torch
$ rye add transformers

torch か tersorflow が入ってないとエラーになった。逆に言うとどっちかでいい。

run.py
from transformers import pipeline

classifier = pipeline("sentiment-analysis")

res = classifier("私たちは🤗 Transformersライブラリをお見せできてとても嬉しいです。")

print(res)

実行してみる。

$ rye run python run.py
...
[{'label': 'NEGATIVE', 'score': 0.89532071352005}]

動くには動いたが、 transformers ライブラリで型が提供されてないので、エディタが真っ赤になる。気分が悪い。

ちょうどいい機会なので、ググりながら型を付けてみる。

https://mypy.readthedocs.io/en/stable/type_narrowing.html

from typing import TypedDict, cast
from transformers import pipeline  # type: ignore

ClassifyResult = TypedDict("ClassifyResult", {"label": str, "score": float})

classifier = pipeline("sentiment-analysis")

result = cast(list[ClassifyResult], classifier("私たちは🤗 Transformersライブラリをお見せできてとても嬉しいです。"))

print(result[0]["label"], result[0]["score"])

握りつぶしたい場合、# types: ignore または from typing import Any; res: Any = ... でどうにかできそう。

TypeScript プログラマのための mypy チートシートみたいなのがあると便利な気がしてきた。

.vscode/settings.json

色々設定するとキャッシュがプロジェクト内に飛び散っていて視界の邪魔だったので、中身を見る必要がなさそうなものは、そもそも非表示にした。

{
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true
  },
  "python.analysis.typeCheckingMode": "strict",
  "files.exclude": {
    "__pycache__": true,
    ".mypy_cache": true,
    ".pytest_cache": true,
    ".ruff_cache": true,
    ".venv": true,
    "requirements*.lock": true
  }
}

rye tools でコマンドをインストールする

https://rye.astral.sh/guide/tools/

rye tools install ... でグローバルコマンドをインストールできる。

$ rye tools install pycowsay
$ pycowsay

今回は cover-agent を入れたくて、 git からインストールする必要があったので、こうなった

$ rye tools install cover-agent --git https://github.com/Codium-ai/cover-agent.git

次にやること

  • TypeScript プログラマのための mypy or pyright 入門書いてみる => 別にいらないな...
  • huggingfaceのモデルを動かす
  • whisper を動かして cloudflare tunnel 経由でホストする
  • mac で mlx のモデルを動かす
  • ollama 動かす
  • Windows の LM Studio に Mac から接続

他、これ入れとけってのがあったら教えてほしい。

Discussion