ryeをWindowsにインストールする

ryeをWindowsにインストールする方法を調べてみました。

準備

rye をWindowsで使うためには、開発者モードを使うことが推奨されている。

FAQ - Rye

開発者モードを有効にしないとシンボリックリンクが使えなくてジャンクションポイントやコピーが使われるようになるそうだ。

公式インストーラ

Installation - Rye

ここから rye-x86-64_windows.exe をダウンロードしてダブルクリックで実行すると、コマンドプロンプトが表示されてオプションを選択していく。全てデフォルトにするのがおすすめ。最新安定版のpythonをダウンロードしてくれるし、PATHも自動で設定してくれる。

インストーラーをコマンドから実行する場合は --yes オプションをつけるとデフォルトにしてくれる。

winget

winget install rye でインストールすると公式インストーラーを使って全てデフォルトでインストールしてくれる。ただし winget uninstall rye でアンインストールに失敗してしまった。

winget uninstall rye のエラー

rye self uninstall でアンインストールできるが、次の winget install rye で Rye がすでにインストールされているというエラーになってしまうので、 winget install -f rye が必要になる。

アンインストールがコマンドからできないとなると、メリットが

Scoop

scoop install rye をすると、公式インストーラーを使ってインストールする。アンインストールも問題なく動作した。

使うオプションはほぼデフォルトだが、インストールするディレクトリがデフォルト $HOME/.rye ではなく $HOME/scoop 配下になる。普段使いでは問題ないが、たとえば rye が管理している python を直接使いたい場合にはちょっとパスが長くなる。

まとめ

普段wingetかScoopを使っているならそれを、使っていないのであれば公式インストーラーで問題ないと思います。

パッケージマネージャーを使う利点の1つはアップデートとアンインストールが楽になることですが、Ryeの場合は rye self updaterye self uninstall があるのでパッケージマネージャーを使わない運用も比較的楽だと思います。

Goのデータベースドライバが壊れた接続を消す方法

database/sql が壊れた接続をコネクションプールから削除するための、昔からある流れは、Query()やExec()が driver.ErrBadConn を返すと、database/sqlはその接続を閉じてリトライするというものです。 ErrBadConnには接続が壊れたことを示す役割とリトライするべきことを示す役割を兼ねているので、たとえばクエリを投げた後に結果を受信しようとReadしてエラーになった場合はErrBadConnを返すことができず、次にその接続が使われるときにErrBadConnを返すことになりました。

その後、利用し終わった接続を次回再利用するまでの間に状態のリセットなどを可能にしようということで SessionResetter が導入されました。このインターフェイスがErrBadConnを返すことで、次回利用を待つことなく接続を閉じれるようになりました。

SessionResetterが導入された当時は接続をプールに戻してから次にプールから取り出すまでの間に非同期的に呼ばれていました。例えばMySQLの wait_timeout のように時間によって接続が壊れる場合、プールに戻してResetSession()が呼ばれた後に再利用までしばらくの間があき、その間に接続が壊れるという場合があったので、クエリ実行時のErrBadConnはまだ重要でした。

しかし、非同期的な ResetSession() の呼び出しが database/sql のプール管理に余計な複雑さを招いていたので、Go 1.15で呼び出しタイミングが変更されてプールから接続を取り出したときに呼ばれるようになりました。これでクエリの最初にErrBadConnを返さないといけないケースは大分減りました。 また、Go 1.15では driver.Validator というインターフェイスも追加され、 IsValid() が false を返したら接続が壊れたことを伝えられるようになりました。IsValid()はクエリ実行後、コネクションをプールに戻す前に呼び出されるので、クエリ中にコネクションが壊れた場合にそれを database/sql にすぐに伝えられるようになりました。

このため、クエリを実行する Exec(Contect) や Query(Context) が ErrBadConn を返すのは、クエリを実行する過程でエラーが起こったけれどもリトライしても絶対に安全だと言える限られたケースだけになりました。

さらに、Go 1.18からは database/sql が ErrBadConn をチェックするときに err == ErrBadConn ではなく errors.Is(err, ErrBadConn) を使うようになりました。なので独自のエラー型を返す場合でも、例えば Is(ErrBadConn) が true を返すようにすれば ErrBadConn を返すのと同じくリトライをさせることができます。しかし安全にリトライできない場合は接続が壊れたことは IsValid() で示すべきですし、リトライする場合はせっかく独自のエラーを返してもそのエラーは捨てられてしまうので、手間をかけてErrBadConnと内容のある独自エラーを組み合わせる必要があるのかは疑問です。

まとめ

ドライバーを実装するとき、接続を壊す方法は3つあります。

  • Validator -- コネクションをプールに戻すときに呼ばれるので、 IsValid() で false を返せばその接続は再利用せずに捨てる。
  • Query() 等のエラーで ErrBadConn を返す。 -- 接続が壊れているだけでなく、(トランザクション外なら)安全にリトライできることをdatabase/sqlに教える場合に使う。
  • SessionResetter -- コネクションがプールから取り出されるときに呼ばれるので、アイドル中に接続が壊れたことを検知できたのであれば ErrBadConn を返す。

xor_bytesの高速化

websocketのフレームのマスク処理や暗号化処理などでバイナリデータ同士のxorが必要になることがあります。

Pythonのbytes型はxorを提供していないので自分で実装しないといけないのですが、この時 bytes([x ^ y for (x,y) in zip(data, mask)]) のような内包表記で行うことが多いようです。

https://sourcegraph.com/search?q=context:global+lang:Python+xor_bytes

実は Python 3 には int.from_bytes() と int.to_bytes() があり、うまく使えば内包表記よりも10倍以上高速化できます。

def xor_bytes_list(a, b):
    return bytes([(aa ^ bb) for (aa, bb) in zip(a, b, strict=True)])


def xor_bytes_generator(a, b):
    return bytes((aa ^ bb) for (aa, bb) in zip(a, b, strict=True))


def xor_bytes_via_int(a, b):
    if len(a) != len(b):
        raise ValueError(f"a and b must have same length; {len(a)=} {len(b)=}")
    aa = int.from_bytes(a)
    bb = int.from_bytes(b)
    return (aa ^ bb).to_bytes(len(a))
# MacBook Pro (M1 Pro) で計測
# 入力は256バイト

list: Mean +- std dev: 7.59 us +- 0.12 us
generator: Mean +- std dev: 10.1 us +- 0.3 us
int: Mean +- std dev: 685 ns +- 7 ns

bytes.to_bytes() の引数にもとの長さを指定してやるのがコツです。 エンディアンも指定できる(デフォルトは 'big')ので、うまく使えば長さが違うときに前か後ろを0パディングするような動作も実装できるはずです。

追記: エンディアンPython 3.11から big がデフォルトになったので、3.10まででも動くコードを書く場合は "big" を指定しましょう。

追記2: int を使った方法はPyPyで遅くなってしまいました。PyPyでもCPythonでも速くなる方法として bytes(map(operator.xor, a, b)) があります。

pipのキャッシュの管理

MySQL 8.4がリリースされて、定番の「mysqlclient をインストールしたんだけど動かない」という報告が来た。これはpipが以前にビルドしたバイナリをキャッシュして再利用しているためで、前のバージョンのlibmysqlclientにリンクしたバイナリなのでバージョンアップしたら当然動かなくなる。

この解決方法と合わせて、pipのキャッシュについて簡単に説明していこうと思う。

pip.pypa.io

2種類のキャッシュ

pipのキャッシュには2種類ある。HTTPレスポンスを保存するHTTPキャッシュと、ソースパッケージをビルドした結果を保存するwheelキャッシュだ。

wheelが提供されていたらHTTPキャッシュのみが使われる。wheelキャッシュはソースパッケージからインストールするときしか使われない。

最近はpure PythonパッケージでもWheelを提供することが増えたのでwheelキャッシュの数は増えにくくなった。

一方でmysqlclientのようにユーザーの環境にあるライブラリとリンクするC拡張を含むパッケージはバイナリを提供できないのでwheelキャッシュが使われるが、最初に紹介したような問題の発生源にもなる。

wheelキャッシュを無効化する方法

pipのキャッシュを無効化する方法として --no-cache-dir が有名だけれども、これは問題のあるwheelキャッシュだけでなくHTTPキャッシュまで無効化するのであまり推奨されていない。バイナリのキャッシュを使わないために公式に推奨されているのは次の方法だ。

python -m pip download sampleproject==1.0.0 --no-binary :all:
python -m pip install sampleproject-1.0.0.tar.gz

It is also a good idea to remove the offending cached wheel using the pip cache command.

Avoiding caching

1つ目の方法は pip download --no-binary :all: してソースパッケージをダウンロードしてから、pip installでそのソースパッケージをインストールする。ローカルのパッケージのインストールではバイナリキャッシュを使わないので問題が起こらない。

2つ目の方法は pip cache コマンドを使って wheel を消すことだ。 mysqlclient の例で言えば、 pip install mysqlclient する前に pip cache remove mysqlclient で mysqlclient の wheel キャッシュを削除してしまう。パッケージ名はglobパターンを使えるので、 pip cache remove '*' (*はシェルによる置換を避けるためにクォートが必要) で全パッケージのwheelを削除してもいい。

pip cache コマンド

pip cache dir, pip cache info

pip cache dir コマンドはキャッシュディレクトリを表示する。この中に http, http-v2, wheels ディレクトリがある。ちなみに http ディレクトリは古いフォーマットのキャッシュなので、古いpipを使ってないのであれば消していい。

pip cache info コマンドを使うともっと詳細な情報を取得できる。httpキャッシュとwheelsキャッシュそれぞれのパスはもちろん、キャッシュの容量や数も表示してくれる。

pip cache list, pip cache remove

list と remove は wheel キャッシュのためのコマンドだ。

list を引数なしで実行したら全てのキャッシュを表示するし、引数に glob パターンを渡すとそれにマッチするキャッシュだけを表示する。

remove コマンドはglobパターンで指定されたキャッシュを削除する。

pip cache purge

httpキャッシュとwheelキャッシュ両方を削除する。

httpキャッシュはwheelキャッシュと違って容量を使う以外のデメリットがないので、容量を気にする時以外は purge よりも pip cache remove '*' の方が良い。

Google Docs の日本語フォント設定と行間

Google DocsでデフォルトのフォントはArialだが、Arialは英字フォントなので日本語部分はブラウザの「標準フォント」が使われる。ページ分けしないDocsに議事録を書く場合などはいいが、ページ分けしたDocsやプレゼンテーションは閲覧者が同一のフォントで見た方がいいだろう。

フォント選択ドロップダウンリストの一番下に、Macだと「ヒラギノ丸ゴ ProN」「ヒラギノ角ゴ ProN」、Windowsだと「メイリオ」「MSPゴシック」「MSP明朝」などが並んでいるが、これもユーザーのOSごとに違うフォントになるのでユーザーごとにフォントを統一する目的では利用できない。

フォント選択のドロップダウンから「その他のフォント」を選ぶと追加フォント選択ダイアログが出てくる。この画面で「文字: 日本語」を選ぶと日本語に対応したフォントが選べる。

追加フォント選択画面

気に入ったフォントを選んだら、スタイル選択リストの下にある「「標準テキスト」をカーソル位置のスタイルに更新」を選択することで、以後の標準テキストのフォントを設定できる。タイトルや見出しのフォントも、デフォルトのままなら標準テキストのものに自動で変更される。

標準テキストのスタイル保存

新規ドキュメントでも同じスタイルを使いたい場合は、同じスタイル選択リストの一番下にある「オプション」から「デフォルトのスタイルとして保存」を選ぶ。複数のスタイルは保存できないので、スタイルを使い分けたい場合はテンプレートとなる文書を保存しておこう。

デフォルトのスタイルを設定する

ためしにいくつかの日本語フォントを並べてみた。全てフォントサイズは12で行間は1だ。 多くの日本語フォントの行間が大きいのが気になる。これがバグなのかこういうものなのかはわからないが、スタイルがあまり崩れないようにするためには行間が狭いフォントを選び、行間が広いフォントを選ぶ場合はスタイルを調整する必要がありそうだ。

Google Docsの日本語フォントその1

Google Docsの日本語フォントその2

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。