Arch 系 Linux pip 安装到 /usr/local 的方法

Linux 系统目录结构中,由系统包管理程序管理的文件一般存放在 //usr 中,/usr/local 则存放不受系统包管理程序管理的文件,例如通过 make install 手动安装的程序、使用 pip 等其他程序安装的程序。Debian/Ubuntu 等发行版对 python 包进行了一定的修改,由 pip 安装的包默认会安装在 /usr/local 中。而 Arch/Manjaro 等发行版保持了上游的“原汁原味”,对上游的代码做了最小的修改,因此保留了 Python 默认的行为。在这些发行版中,pip 安装的包会安装在 /usr/lib/pythonX.Y/site-packages 中。为了防止 pip 和系统包管理冲突,最好通过配置使 pip 像 Debian/Ubuntu 一样把包安装到 /usr/local 中。

目标

设置方法

假设系统是全新安装的,只安装了 pythonpython2 等包,没有安装 pip,没有用 pip 安装其他包。

按步骤进行:

在所有版本 Python 的默认 site-packages 目录中创建 .pth 文件。例如,对于 Python 3.8 版本,在 /usr/lib/python3.8/site-packages/ 目录中创建 usr-local.pth 文件,内容为:

/usr/local/lib/python3.8/site-packages

注意:pythonX.Y 需要和实际情况一致。

编辑 root 用户的 ~/.config/pip/pip.conf 文件,设置 install.prefix 配置项。同时可以顺便换一下源。编辑的结果为:

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple

[install]
prefix = /usr/local

然后即可按照官方文档的方法,使用 root 用户下载 get-pip.py,正常安装 pip,使用 pip 安装其他包。

注:洒家使用的是 Manjaro,理论上 Arch 也能用。

其他方案

如果自己的使用场景目标和前述 5 个目标不完全重合,还有以下几种替代方案。

--user 参数

如果只有一个用户运行 Python,可以使用 pipget-pip.py 都接受的参数 --user,把 pip 和其他包安装在 ~/.local/lib/ 中。

缺点是只安装在了自己的家目录中,并且每次安装都需要加参数。

使用虚拟环境

使用 pyenvvirtualenv 等,不再赘述。

解释

对比 Arch 2020-4-8 3.8.2-2 版本Ubuntu 2020-4-30 3.8.2-1 版本Python 原版site.py 文件,可以发现,Debian/Ubuntu 等发行版对 python 包进行了一定的修改,而 Arch/Manjaro 等发行版保持了上游的“原汁原味”,对上游的代码做了最小的修改。

Ubuntu 的头部增加了一段注释:

For Debian and derivatives, this sys.path is augmented with directories
for packages distributed within the distribution. Local addons go
into /usr/local/lib/python<version>/dist-packages, Debian addons
install into /usr/lib/python3/dist-packages.
/usr/lib/python<version>/site-packages is not used.

getsitepackages 函数有区别,Ubuntu 版为:

def getsitepackages(prefixes=None):
    """Returns a list containing all global site-packages directories.

    For each directory present in ``prefixes`` (or the global ``PREFIXES``),
    this function will find its `site-packages` subdirectory depending on the
    system environment, and will return a list of full paths.
    """
    sitepackages = []
    seen = set()

    if prefixes is None:
        prefixes = PREFIXES

    for prefix in prefixes:
        if not prefix or prefix in seen:
            continue
        seen.add(prefix)

        if os.sep == '/':
            if 'VIRTUAL_ENV' in os.environ or sys.base_prefix != sys.prefix:
                sitepackages.append(os.path.join(prefix, "lib",
                                                 "python" + sys.version[:3],
                                                 "site-packages"))
            sitepackages.append(os.path.join(prefix, "local/lib",
                                             "python" + sys.version[:3],
                                             "dist-packages"))
            sitepackages.append(os.path.join(prefix, "lib",
                                             "python3",
                                             "dist-packages"))
            # this one is deprecated for Debian
            sitepackages.append(os.path.join(prefix, "lib",
                                             "python" + sys.version[:3],
                                             "dist-packages"))
        else:
            sitepackages.append(prefix)
            sitepackages.append(os.path.join(prefix, "lib", "site-packages"))
    return sitepackages

而 Arch 版为:

def getsitepackages(prefixes=None):
    """Returns a list containing all global site-packages directories.

    For each directory present in ``prefixes`` (or the global ``PREFIXES``),
    this function will find its `site-packages` subdirectory depending on the
    system environment, and will return a list of full paths.
    """
    sitepackages = []
    seen = set()

    if prefixes is None:
        prefixes = PREFIXES

    for prefix in prefixes:
        if not prefix or prefix in seen:
            continue
        seen.add(prefix)

        if os.sep == '/':
            sitepackages.append(os.path.join(prefix, "lib",
                                        "python%d.%d" % sys.version_info[:2],
                                        "site-packages"))
        else:
            sitepackages.append(prefix)
            sitepackages.append(os.path.join(prefix, "lib", "site-packages"))
    return sitepackages

实际运行命令也有区别,在 Ubuntu 20.04 中:

Python 3.8.2 (default, Apr 27 2020, 15:53:34)
[GCC 9.3.0] on linux

>>> site.getsitepackages()
['/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.8/dist-packages']
>>> sys.path
['', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']

在 Manjaro 中:

Python 3.8.3 (default, May 17 2020, 18:15:42)
[GCC 10.1.0] on linux

>>> site.getsitepackages()
['/usr/lib/python3.8/site-packages']
>>> sys.path
['', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/usr/lib/python3.8/site-packages']

解决方法就是,配置 pip,修改其默认 prefix,使 pip 安装到 /usr/local 中。由于 sys.path 不包含 /usr/local 中的安装目录,还需要增加一个 .pth 文件,从而保证安装的包能被正常加载运行。

参考和扩展阅读

原版源代码:

官方文档:

相关讨论:


Advertisements

Comments

License

Creative Commons License

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).