---
title: "用 Wasmer 运行 Python 并安装依赖"
canonical_url: "https://yangqiuyi.com/blog/kai-fa/yong-wasmer-yun-xing-python-bing-an-zhuang-yi-lai"
---

> Index of every page on this site: [llms.txt](https://yangqiuyi.com/llms.txt)

# 用 Wasmer 运行 Python 并安装依赖

## 什么是 Wasmer

[Wasmer](https://wasmer.io/) 是一个 WebAssembly 运行时，可以把各种语言编译成 `.wasm` 然后在任何地方运行。它社区维护了一个 Python 的 WASIX 构建，让你可以在不安装 Python 的机器上跑 Python 脚本：

```bash
wasmer run python/python@3.13 -- -c "print('hello world')"
```

一行命令，不需要安装 Python，不需要管版本。这在 CI、临时脚本、或者不想污染本地环境时特别有用。

## 问题：怎么装依赖

直接用 `pip install` 是不行的——WASIX 构建的 Python 缺少 `mmap` 等 C 模块，pip 的 install 命令跑不起来。

但换个思路：**不用 pip，直接用 Python 标准库下载 wheel 解压到 site-packages**。Python 的 wheel 本质上就是个 zip 包，解压到 `site-packages` 目录就能 import。

## 两类依赖

Python 包分两种：

1. **纯 Python 包**（如 `pytz`、`six`）：wheel 里只有 `.py` 文件，任何平台都能用，从 PyPI 下载即可
2. **含 C 扩展的包**（如 `numpy`、`pandas`）：wheel 里有编译好的 `.so`/`.dll`，需要对应平台的预编译版本

对于第二类，Wasmer 社区维护了一个 [WASIX Python 索引](https://pythonindex.wasix.org/simple/)，提供了 60+ 常用包的 WASIX 预编译 wheel。

## 完整方案

下面这个脚本可以在 Wasmer Python 里直接运行，自动处理两类依赖：

```python
import sys, os, zipfile, urllib.request, re, tempfile, json

site_packages = os.path.join(
    sys.base_exec_prefix, 'lib',
    f'python{sys.version_info.major}.{sys.version_info.minor}',
    'site-packages'
)
os.makedirs(site_packages, exist_ok=True)

def install(name):
    # 先试 WASIX 索引（含 C 扩展的包）
    try:
        url = f'https://pythonindex.wasix.org/simple/{name}/'
        html = urllib.request.urlopen(url, timeout=30).read().decode()
        links = re.findall(
            r'href="([^"]+cp313-cp313-wasix_wasm32\.whl[^"]*)"', html
        )
        if links:
            target = ('https://pythonindex.wasix.org/packages/'
                      + links[0].split('/')[-1].split('#')[0])
            print(f'[wasix] {name}...')
            resp = urllib.request.urlopen(target, timeout=120)
            tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.whl')
            tmp.write(resp.read()); tmp.close()
            with zipfile.ZipFile(tmp.name) as zf:
                zf.extractall(site_packages)
            os.unlink(tmp.name)
            return True
    except:
        pass

    # 再从 PyPI 下载纯 Python wheel
    data = json.loads(
        urllib.request.urlopen(f'https://pypi.org/pypi/{name}/json').read()
    )
    ver = data['info']['version']
    for f in data['releases'].get(ver, []):
        fn = f['filename']
        if 'py3-none-any' in fn and fn.endswith('.whl'):
            print(f'[pure] {name} {ver}...')
            resp = urllib.request.urlopen(f['url'], timeout=60)
            tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.whl')
            tmp.write(resp.read()); tmp.close()
            with zipfile.ZipFile(tmp.name) as zf:
                zf.extractall(site_packages)
            os.unlink(tmp.name)
            return True

    print(f'[FAIL] {name}')
    return False
```

然后正常使用：

```python
# 安装依赖
for pkg in ['pytz', 'six', 'python-dateutil', 'numpy', 'pandas']:
    install(pkg)

# 使用
import numpy as np
import pandas as pd

print(np.array([1, 2, 3]) * 10)
print(pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}))
```

## 运行

把上面的代码保存为 `script.py`，然后：

```bash
wasmer run python/python@3.13 --net -- script.py
```

<warning>

需要加 `--net` 参数开启网络访问，否则无法下载 wheel。

</warning>

## 依赖下载到哪了

wheel 解压后去了哪？看代码里的 `site_packages` 变量：

```python
site_packages = os.path.join(
    sys.base_exec_prefix, 'lib',
    f'python{sys.version_info.major}.{sys.version_info.minor}',
    'site-packages'
)
```

`sys.base_exec_prefix` 在 Wasmer Python 里是 `/usr/local`，所以最终路径是：

```text
/usr/local/lib/python3.13/site-packages/
```

这个路径**在 WASM 虚拟机的文件系统里**，不在你的宿主机上。每次 `wasmer run` 都是一个全新的环境，上次装的包不会保留。

如果你希望依赖持久化，可以把 `site-packages` 挂载到宿主机目录：

```bash
# 第一次运行，装依赖
wasmer run python/python@3.13 --net \
  --volume ./site-packages:/usr/local/lib/python3.13/site-packages \
  -- script.py

# 之后运行，直接用已装好的依赖
wasmer run python/python@3.13 \
  --volume ./site-packages:/usr/local/lib/python3.13/site-packages \
  -- script.py
```

这样 wheel 会被解压到宿主机的 `./site-packages/` 目录，下次运行时直接挂载即可，不需要重新下载。

## 完整示例

把依赖安装和脚本运行分开，第一次装依赖，之后直接跑：

```bash
# 第一步：装依赖（写入宿主机 ./site-packages/）
wasmer run python/python@3.13 --net \
  --volume ./site-packages:/usr/local/lib/python3.13/site-packages \
  -- -c "
import sys, os, zipfile, urllib.request, re, tempfile, json

site_packages = '/usr/local/lib/python3.13/site-packages'
os.makedirs(site_packages, exist_ok=True)

def install(name):
    try:
        url = f'https://pythonindex.wasix.org/simple/{name}/'
        html = urllib.request.urlopen(url, timeout=30).read().decode()
        links = re.findall(r'href=\"([^\"]+cp313-cp313-wasix_wasm32\.whl[^\"]*)\"', html)
        if links:
            target = 'https://pythonindex.wasix.org/packages/' + links[0].split('/')[-1].split('#')[0]
            print(f'[wasix] {name}...')
            resp = urllib.request.urlopen(target, timeout=120)
            tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.whl')
            tmp.write(resp.read()); tmp.close()
            with zipfile.ZipFile(tmp.name) as zf: zf.extractall(site_packages)
            os.unlink(tmp.name)
            return True
    except: pass
    data = json.loads(urllib.request.urlopen(f'https://pypi.org/pypi/{name}/json').read())
    ver = data['info']['version']
    for f in data['releases'].get(ver, []):
        fn = f['filename']
        if 'py3-none-any' in fn and fn.endswith('.whl'):
            print(f'[pure] {name} {ver}...')
            resp = urllib.request.urlopen(f['url'], timeout=60)
            tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.whl')
            tmp.write(resp.read()); tmp.close()
            with zipfile.ZipFile(tmp.name) as zf: zf.extractall(site_packages)
            os.unlink(tmp.name)
            return True
    print(f'[FAIL] {name}')

for pkg in ['pytz', 'six', 'python-dateutil', 'numpy', 'pandas']:
    install(pkg)
"

# 第二步：运行脚本（直接用已装好的依赖）
wasmer run python/python@3.13 \
  --volume ./site-packages:/usr/local/lib/python3.13/site-packages \
  --volume ./script.py:/script.py \
  -- script.py
```

## 为什么 pip 不能直接用

WASIX 构建的 Python 缺少 `mmap` 模块（pip 内部依赖它来做缓存），所以 `pip install` 跑不了。但 `ensurepip` 模块是有的，可以提取出 pip 本身——只是 install 命令不可用。

## 版本注意事项

- 需要用 `python/python@3.13`，因为 WASIX 索引的 wheel 都是 `cp313` 的
- `wasmer/python` 是 3.12 版本，对应的 WASIX wheel 较少
- 如果包在 WASIX 索引里找不到，那它要么没有 C 扩展（走 PyPI 纯 Python wheel），要么就是不支持

## 总结

在 Wasmer 里跑 Python 并安装依赖的关键：

1. **不用 pip**，用 `urllib` + `zipfile` 手动下载解压 wheel
2. **含 C 扩展的包**从 `pythonindex.wasix.org` 下载
3. **纯 Python 包**从 PyPI 下载 `py3-none-any` wheel
4. 运行时加 `--net` 开启网络
5. 用 `--volume` 把 `site-packages` 挂载到宿主机，实现依赖持久化

这样就能在完全不需要本地 Python 环境的情况下，运行任何 Python 脚本了。

## Sitemap

See the full [sitemap](https://yangqiuyi.com/sitemap.md) for all pages.
