Python + VSCode の環境構築 20240604
作業メモ。モダン Python 速習。
AI 周りのツールを動かしていたら TypeScript だけでやるには無理が出てきたので、久しぶりに Python の環境構築をする。
具体的には TestGen LLM を動かしたい。
Python はたまに触るけど、基本 2.x 時代の知識しかない。
基本的にこの記事を読みながら、細かいアレンジをしている。
追記
- rye が ruff と pytest を同梱してるので
rye fmt
,rye check
,rye test
で良かった - uvicorn を叩くより、 fastapi-cli を使って起動したほうが良さそうので変更
基本方針: Rye に全部任せる
良く出来てると噂に聞いたので、 rye に任せる。
自分が Python が苦手な点は pip を下手に使うと環境が汚れていく点で、基本的に rye で閉じて管理させる。システムの Python には指一本触れさせない。
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 拡張をいれる。
onSave でフォーマットするようにしておく。
{
"[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 から見るために拡張をいれる。
インストールすると 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 を写経したときのだが、そのときにほしかった。
うろ覚えになってるので、もう一回チャレンジするのもいいかも。
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
これを参考に
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 に近い印象。
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 を経由して型をつけて叩きやすくしておく。
これを参考にしている。
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 のラッパーを使う
(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 が入ってないとエラーになった。逆に言うとどっちかでいい。
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
res = classifier("私たちは🤗 Transformersライブラリをお見せできてとても嬉しいです。")
print(res)
実行してみる。
$ rye run python run.py
...
[{'label': 'NEGATIVE', 'score': 0.89532071352005}]
動くには動いたが、 transformers ライブラリで型が提供されてないので、エディタが真っ赤になる。気分が悪い。
ちょうどいい機会なので、ググりながら型を付けてみる。
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 でコマンドをインストールする
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
TypeScript プログラマのための mypy 入門、書こうと思ったけどこれ見て満足してしまった
rye には ruff がデフォルトで入っているので
で大丈夫です
source: https://rye.astral.sh/guide/commands/fmt/ , https://rye.astral.sh/guide/commands/lint/
pytestはdependencyに入れないといけないですが、公式にサポートしてます
source: https://rye.astral.sh/guide/commands/test/