スキップしてメイン コンテンツに移動

投稿

ラベル(python)が付いた投稿を表示しています

pipenv の zsh 補完

Django の開発中の不満の一つが pipenv run の補完だ。 zsh は補完が凄いと聞いていたのに bash から移行してストレスが増えた。 bash はとりあえずファイルがそこにあればいつでも補完が効く。 pipenv run python と打った後にはカレントの manage.py が補完できる。 それに対して、zsh の補完はもっと inteligent で文脈を理解するのだが、何の指定もない文脈では何も補完できない。 本当に使えない。 まあお気づきかと思うが、zsh が使えないというより設定ファイルが酷い、という話、のはずだ。 調査 まずは状況を確認しよう。 Mac の homebrew で pipenv を入れていて、シェルは zsh。 .zshrc で補完の設定は最低限のものだけ。 autoload -Uz compinit && compinit zstyle ':completion:*' completer _complete _ignored _files .zshrc pipenv の補完設定は /usr/local/share/zsh/site-functions/_pipenv にあるが、これは /usr/local/Cellar/pipenv/2023.11.15/share/zsh/site-functions/_pipenv へのシンボリックリンクだ。 homebrew がこの設定ファイルをどこから持ってきているかというと、pipenv.rb の中で次の補助関数で生成している。 generate_completions_from_executable(libexec/"bin/pipenv", shells: [:fish, :zsh], shell_parameter_format: :click) pipenv.rb generate_completions_from_executable は、formula.rb に定義がある。 shell_param...

pyi スタブ

私は基本的に Python で型を書きたくない人で、 Python で型を書くと関数定義行が長くなって読みにくくなるのが嫌だなというのが型を書きたくない理由の1つだったのだが、 関数の(に限らないけど)型だけ別ファイルに置いておく仕組みがあった。 スタブ(stub)ファイルというもので、実は型を書けるようにする仕組みと同じ時に誕生してたので、知らなかった方がおかしいのかもしれない。 ともかくこれで(保守コストはさておき)型を書くことへの抵抗感はぐっと減った。 別ファイルの型情報を誰がどうやってどこに引っ張ってくるのか、という部分については、型チェッカーを単独で導入するほどのやる気になったわけではないので、VSCode の Pylance というプラグインが勝手にやってくれるに任せることにして、少し書いてみた。 結果は微妙である。 他のモジュールから import した関数などはちゃんと型が見えるのだが、そのファイル内で定義している関数の型はスタブから読まれない。 Unknown な引数を受け取って Unknown な型を返すようにしか示されない。 ネットでざっくり調べたところによると、スタブファイルを使うことでモジュール本体をパースするより手軽に型を取り込める、といった用途で使うものとの主張を見た。 一方で PyCharm は pyi スタブから型を読んで当てはめてくれるらしい。 こっちの方が直観的には便利。 理想的には、py + pyi とインラインで型を付けた py と自由に行き来できたら幸せ。 妥協案として、インラインで型を書いても良いけど「アノテーションを隠す」モードが提供されたらだいぶマシ。 VSCode にそんなプラグインがあったりするかなと思ったけど、editor.semanticTokenColorCustomizations で色を変えたり bold/italic にしたりがカスタマイズの限界なので難しいか。

functools.cache とフィボナッチ

フィボナッチ数を計算する関数を再帰で素直に書く。 def fib(n): if n < 0: return n return fib(n-1) + fib(n-2) このままではものすごく遅いので、メモ化すると良い。 というのは一般常識の内だろう。 Python には 3.9 からデコレーター functools.cache があるので、簡単だ。 from functools import cache @cache def fib(n): if n < 0: return n return fib(n-1) + fib(n-2) もう少し前のバージョンを使わなければならない人も安心して欲しい。 デコレーター functools.lru_cache が 3.2 から存在していて、 lru_cache(maxsize=None) と書けば実質的に cache と同じことをしてくれる。 実は上のフィボナッチ数を計算するプログラムは functools 公式ドキュメントの lru_cache 使用例から持ってきたものだ。 話はちょっと飛ぶのだが、昔やっていた NZMATH というプロジェクトをもう一度動かそうという動きがあるとかないとか。 いや動きがあるとは聞いているのだが、実際に動き出したかどうかはまだ見えていない。 そんな NZMATH にもフィボナッチ数を計算する関数 nzmath.sequence.fibonacci があった。 FIBONACCI = {0:0, 1:1} def fibonacci(n): """ param non-negative integer n return the n-th term of the Fibonacci effect FIBONACCI[n] = fibonacci(n) """ if n < 0: raise ValueError("fibonacci(n) 0 <= n ?") if n in FIBONACCI: retu...

lsp-mode と python-language-server と pipx

長く Emacs でプログラムを書いている。 最近は(?)時代遅れ扱いされがちな Emacs ではあるが、特に乗り換えたいというほどのものがなく、何となく使い続けている。 他の環境で使えるようになった良さげな機能があって食指が動いても、それ Emacs でもできるよ、と言われると、じゃあいいかとなる。 今回、導入してみたいのは python-language-server である。 LSP (language server protocol) 自体は、VS Code のためにか何か Microsoft が作ったものだそうだが、 プロトコルの規定なので結局それを Emacs だったり Vim だったりから使えるようにする人が現れて、事実上の標準になっている。 昔何か別のコード補完のパッケージを Emacs に入れたときは重すぎて使い物にならず、 いままで dabbrev 以外の補完は使ってこなかったのだが、これはどうだろうか。 導入に際して引っかかるのは、python-language-server のインストール先である。 だいたい、Python のプロジェクトは、少し前まで Python 2 と 3 を使うものが混在していたために必要に迫られて、 pipenv で環境を切り分けて依存から何から別々に持つ、という方針で扱ってきたので、 その方針でいくとプロジェクトごとかもしれない、と最初は考えた。 しかし、Emacs でこれを使う lsp-mode の設定はパスを一つ指定する必要があるようだ。 どうしよう、と、ここで1ヶ月は放置していたと思う。 そんなある日 pipx というものを知った。 Python で書いたコマンドを隔離した環境にインストールするという。 要するに1つのコマンド用に venv を切ってインストールしてパスを通す、みたいなことをコマンド一発でやってくれるというわけだ。 これだわ。 ということで、インストール方法は pipx をインストールする。たとえば mac なら brew install pipx でいける。 pipx install python-language-server Emacs に lsp-mode を入れる。普通に package-list-packages から選択。 .emacs ...

コメントの文脈

正しいコメント文とは何かを考えていた。 正直に言うと、コメント文をコードから追い出して別レイヤーに配置する方法を考えていた。 それにはまず正しいコメント文のあり方を把握しなければならない。 コメント文は色々と文句を言われるが、結局実行に影響を与えないので、捨て置かれているのだと思う。 文句はたとえば、多すぎる、少なすぎる、コードの実態と合わなくなる、何に対するコメントか不明確、などだ。 最後の文句の解消を考えよう。 まともなコメントであれば、それは何らかの対象に関するもののはずだ。 たとえば変数の値に対する、特定の値しか使わないとか、単位は何だとか、といった注釈。 たとえば関数の引数や型に関する説明、処理内容や、アルゴリズムの選択について。 たとえばコードブロックの役割や、不変量の説明。 したがって、コメントはその説明される対象と同じスコープを持つと思って良い。 スコープを持つものだとすれば、オブジェクトであればよいだろう。 ただ、コメントされる対象と一緒に初期化されるだけのオブジェクトでは、 コードブロック特にコメントされる対象が単に意味的なまとまりとしか言えない数行のコードブロックの場合に、不明確である。 従って、逆にコメントがスコープを作り出すほどの力を持っていた方が適切とも言える。 ここから話は Python に限定される細道に入る。 他の言語では変数スコープの規則が違って、やりにくいかもしれない。 という注意を先にしておこう。 Python ではコンテキストマネージャーという仕組を使うことで、with 文により好き勝手にスコープを切れる。 このとき、変数のスコープはあくまで関数単位(関数の外にあるものはモジュール単位)になるので、このスコープはあくまでコンテキストマネージャーによって提供されるコンテキストの話である。 コメントをコンテキストマネージャーにすることで、コメントの影響範囲は明確化できる。 たとえば、 # A をする a = A() # B をする if a に関する条件: B(a, 追加の引数) else: B(a, 違う引数) のようなコードが、 with Comment("A をする"): a = A() with Comment("B をする"): if a ...