【Python】感動までしたはずのuvを忘れてPixiに心酔した一つの理由
uv よりPixi
今や私が
しかし現在の私は、
uv は何が素晴らしいのか
あらすじ:そもそも
思うに
ここで言う仮想環境とは、
雑な言い方をすれば、「自分のためだけの
uv
仮想環境と前提として、インターネットから
今となっては有名な話になったものと思って居りますが、
$ pip install numpy
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
For more information visit http://rptl.io/venv
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
これは不具合でも嫌がらせでもなく、「公共物を私的に使うな」とでも言ったところです。これを回避するために使うものが、件の仮想環境です。
公式のやり方は煩雑
公式のやり方としては、次のようにして仮想環境を作るのが一般的でしょう。
python3 -m venv
しかしvenv
というものが存在しない場合もあります(存在する場合もある)。ないのですから、python3-venv
をインストールする必要があります(存在する場合は不要)。
sudo apt install python3-venv -y
改めて、先のコマンドで仮想環境を作ります。すると、.venv
という隠しフォルダーが作られます。これで漸く仮想環境が作られましたが、更に、この仮想環境を作動させなければなりません。
source .venv/bin/activate
これで漸く
pip install numpy
但しこれは仮想環境であるために過ぎず、source
コマンドでactivate
した状態になっていなければ、numpy
も使えません。
この作業には、apt
とsource
)が使われています。終始「何を言っているのだろう」、「何をさせられているのだろう」と感じた人も多いのではないでしょうか。私が初めてこれを見た際には、何も理解できませんでした。
呆れることに、何とかこの順序に慣れたとしても、なおも問題は残ります。仮想環境作成に掛かる時間が微妙に長く、微々たるストレスからは逃れられないようになっています。
uv はその煩雑を解消してみせた
-
のインストールuv
curl -LsSf https://astral.sh/uv/install.sh | sh
# curlが使えない場合はwget
wget -qO- https://astral.sh/uv/install.sh | sh
- 「プロジェクト」を作る
要するにフォルダー分けして整理整頓するというのが、プロジェクトという概念です。関係ないプログラムがデスクトップに散乱するようなことを避けるものとも言えるでしょう。このあたりから、従来の
# uv init プロジェクト名
uv init uv_prj
- 「モジュール」をインストールする
先にも例にしていたnumpy
をインストールする場合、このようになります。
$ uv add numpy
Using CPython 3.12.3
Creating virtual environment at: .venv
Resolved 2 packages in 712ms
Prepared 1 package in 2.18s
Installed 1 package in 515ms
+ numpy==2.3.3
「プロジェクト」という概念に戸惑うかもしれませんが、python3 -m venv
からも、source
からも解放されました。更に、仮想環境が作られるのに掛かる時間も大幅に短縮されており、ストレスに感じる点が悉く解消されたのです。
cargo とuv
私が
cargo のpackage とcrate
- 「パッケージ」を作る
cargo new sample
- 「クレート」をインストールする
これは「パッケージ」の中で行われます。インストールしたものはこの「パッケージ」でのみ有効であり、他に影響しません。
cargo add numpy
uv のproject とmodule
- 「プロジェクト」を作る
uv init sample
- 「モジュール」をインストールする
これは「プロジェクト」の中で行われます。インストールしたものはこの「プロジェクト」でのみ有効であり、他に影響しません。
uv add numpy
uv にできなかったこと
実行方法の統一
プログラムを実行するにあたって、毎回次のように入力することが徐々に煩わしく感ぜられるのでした。
uv run main.py
あるいは、src
フォルダーを作っていたとしたら。
uv run ./src/main.py
cargo run
後始末
更に、「パッケージ」にインストールしたものや、作成されたもの(実行ファイルなど)は、次のコマンドで一掃できます。
cargo clean
uv clean
# あるいは
uv cache clean
uv はPython らしさを尊重した?
- 実行時にファイル名を指定する必要がある
- 仮想環境が削除されずに残留する
これらは従来の
Pixi はuv を凌駕する
本題:
-
をインストールするPixi
curl
またはwget
でインストールし、ターミナルを読み込み直します。
curl -fsSL https://pixi.sh/install.sh | sh
# または
wget -qO- https://pixi.sh/install.sh | sh
-
を作るworkspace
Creates a new workspace
とあるため、ここでは
pixi init sample
-
モジュールをインストールするPython
またもや、numpy
をインストールしてみましょう。
pixi add numpy
uv を過去のものにした機能:Task
パッケージ管理ツールとしては珍しい機能として、「タスク」というものがあります。「タスク」と言っても非常に単純な機能で、「簡略化」というニュアンスの方が近いかもしれません。
特別仰々しく大層な機能というわけではないかと思いますが、
Python での便利な使い方
プログラムを実行する際には、次のようになります。
# windowsでは python
pixi run python3 src/main.py
「タスク」を使わないままでは、pixi.toml
というファイルに「タスク」を登録します。pixi.toml
は、pixi init
を実行した時点で勝手に作られています。
[workspace]
︙
[tasks]
# windowsでは python
main = "python3 src/main.py"
test = "python3 test/test_main.py"
[dependencies]
自動登録
コマンドで登録することもできます。
# pixi task add タスクの名前 タスクで実行するコマンド
pixi task add main "python3 src/main.py"
python3 src/main.py
というコマンドにmain
という名前を付けて簡略化できるようになりました。つまり、実行コマンドは次のようになります。
pixi run main
更に、test/test_main.py
というテスト用のプログラムの実行もpixi run test
と簡略化できるようになります。「タスク」の登録こそ必要であるものの、実行ファイル名の省略は、
複雑な使い方の例
突然ですが、
# ワークスペースを作る
> pixi init pixi_docker
# ワークスペースに移動する
> cd pixi_docker
# Dockerをインストールする
> pixi add docker
次を見れば、確かに異なる
# 元々あったDocker
> docker --version
Docker version 24.0.6, build ed223bc
# PixiでインストールしたDocker
> pixi run docker --version
Docker version 20.10.9, build 591094d
hello-world
先ずは本当に動くか確認したいので、hello-world
コンテナで試験しましょう。こちらの記事を参考にしました。
-
hello-world
の を取り寄せるimage
docker pull
のタスクを登録しておきます。
[workspace]
︙
[tasks]
pull_hello_world = "docker pull hello-world"
[dependencies]
このタスクを実行すれば、hello-world
のイメージがダウンロードされます。
> pixi run pull_hello_world
Pixi task (pull_hello_world): docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:54e66cc1dd1fcb1c3c58bd8017914dbed8701e2d8c74d9262e26bd9cc1642d31
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
> pixi run docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 7 weeks ago 10.1kB
bitnami/moodle 4.5 4c9bb25125b7 10 months ago 765MB
bitnami/mariadb 11.4 1b79cab0fb6f 10 months ago 433MB
alpine 3.16.3 bfe296a52501 2 years ago 5.54MB
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 7 weeks ago 10.1kB
bitnami/moodle 4.5 4c9bb25125b7 10 months ago 765MB
bitnami/mariadb 11.4 1b79cab0fb6f 10 months ago 433MB
alpine 3.16.3 bfe296a52501 2 years ago 5.54MB
本当に起動するか確認してみましょう。IMAGE ID
に1b44b5a3e06a
とあるのを使っています。
> pixi run docker run 1b44b5a3e06a
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
動いたようです。
後始末
動くことが分かれば用もないため、削除します。
# コンテナを片付ける
> docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
562f0b945e28edcec3ae292403709c1a5a7ab0441de69480cb9e298eccf77d70
a1a2ccebc7279a2279367bc6677656ae50117aa4d3df857a7cd49d1b66d7cc2e
39099fc0a65c28702aaec4441571638ccb88cacb1e1b855b4585f217026bc7b0
Total reclaimed space: 102.2kB
# イメージを削除する
> docker image rm 1b44b5a3e06a
Untagged: hello-world:latest
Untagged: hello-world@sha256:54e66cc1dd1fcb1c3c58bd8017914dbed8701e2d8c74d9262e26bd9cc1642d31
Deleted: sha256:1b44b5a3e06a9aae883e7bf25e45c100be0bb81a0e01b32de604f3ac44711634
Deleted: sha256:53d204b3dc5ddbc129df4ce71996b8168711e211274c785de5e0d4eb68ec3851
> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
bitnami/moodle 4.5 4c9bb25125b7 10 months ago 765MB
bitnami/mariadb 11.4 1b79cab0fb6f 10 months ago 433MB
alpine 3.16.3 bfe296a52501 2 years ago 5.54MB
MQTT broker
長いのでアコーディオン
ワークスペース構成
# MQTT用パッケージ
pixi add paho-mqtt
# デバッグ用パッケージ
pixi add icecream
[workspace]
︙
[tasks]
# イメージ取り寄せ
pull_eclipse_mosquitto = "docker pull eclipse-mosquitto"
# 起動前に必ず pull_eclipse_mosquitto を実行する
broker = {cmd = "docker compose -f mosquitto/docker-compose.yaml up", depends-on = ["pull_eclipse_mosquitto"]}
publisher = "python src/pub.py"
subscriber = "python src/sub.py"
[dependencies]
docker = ">=20.10.9,<21"
paho-mqtt = ">=2.1.0,<3"
icecream = ">=2.1.8,<3"
version: '3' # 高く設定しすぎると動かない
services:
# mosquittoコンテナについて
broker:
image: eclipse-mosquitto:latest # 取り寄せたmosquittoのイメージ
container_name: broker_container # コンテナの名前
# ホストOS(Windows):Docker のTCP/IPポート対応定義
# これが無いと接続できない
ports:
- 1883:1883
# 設定ファイルをコンテナ側にコピー
# これがないと接続できない
volumes:
- type: bind
source: "mosquitto.conf" # 手許にあるファイル(設定内容記載済み)
target: "/mosquitto/config/mosquitto.conf" # コンテナの中のファイル(初期状態)
# 来るもの拒まず
allow_anonymous true
from icecream import ic
from paho.mqtt.client import Client
from paho.mqtt.enums import CallbackAPIVersion
BROKER = '127.0.0.1'
PORT = 1883
TOPIC = 'pixi/sample'
def subscriber():
ic('start subscriber')
def on_connect(_client, _userdata, _flags, rc, _props):
if rc == 0:
ic("Connected to MQTT Broker!")
else:
ic(f"Failed to connect, return code {rc}\n")
def on_message(_client, _userdata, msg):
ic(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
SUB_ID = 'pixi/subscriber'
client: Client = ic(
Client(
client_id = SUB_ID,
callback_api_version = CallbackAPIVersion.VERSION2,
clean_session = True
)
)
client.on_connect = on_connect
client.on_message = on_message
ic(client.connect(BROKER, PORT))
ic(client.subscribe(TOPIC))
ic(client.loop_forever())
subscriber()
import time
from icecream import ic
from paho.mqtt.client import Client, MQTTMessageInfo
from paho.mqtt.enums import CallbackAPIVersion
BROKER = '127.0.0.1'
PORT = 1883
TOPIC = 'pixi/sample'
def publicher():
ic('start publisher')
def on_connect(_client, _userdata, _flags, rc, _props):
if rc == 0:
ic("Connected to MQTT Broker!")
else:
ic(f"Failed to connect, return code {rc}\n")
PUB_ID = 'pixi/publisher'
client: Client = ic(
Client(
client_id = PUB_ID,
callback_api_version = CallbackAPIVersion.VERSION2,
clean_session = True
)
)
client.on_connect = on_connect
ic(client.connect(BROKER, PORT))
ic(client.loop_start())
count = 0
while True:
time.sleep(3)
msg = ic(f"message {count}")
result: MQTTMessageInfo = ic(client.publish(TOPIC, msg))
status = result[0]
if status == 0:
ic(f"Send `{msg}`")
else:
ic(f"Failed to send message to {TOPIC}")
count += 1
publicher()
pixi run broker
pixi run subscriber
pixi run publisher
動作の様子は
案外便利な特徴・機能
pixi clean
cargo clean
が「パッケージ」内の後始末をしていたように、pixi clean
は「ワークスペース」内の仮想環境と、インストールしたものを削除します。
- 多言語・開発環境対応
-
以外のチャンネルへの対応PyPI
pip install numpy
としても、uv add numpy
としても、numpy
がインストールされます。
pixi add numpy
とした時も、numpy
がインストールされます。numpy
をインストールするには、pixi add numpy --pypi
と明記します。
少し困るところ
-
以外への対応PyPI
例えばopencv-python
であるのに対し、opencv
となります。
prefix.dev
で検索することが最良の選択でしょう。
- インストールした言語と環境との不和
pixi add rust
これで確かに、rust-analyzer
が、どうもこの
rust-analyzer
と
跋
本記事では、
Discussion