使用 flake.nix 为 Python 项目搭建开发环境

setup the DE for the python project with flake.nix

zping

pythonflakebasedpyrightemacs

1333 字

2025-06-08 06:56 +0000


数年中 Python 开发环境的演进概览

这篇是对前几年一篇搭建Python项目开发环境分享的更新,单纯是自己注意到的相关变化。我在上个月,已经把主力操作系统切换到了NixOS下面,现在测试下来,差不多已经可以完全取代之前的工作流。

除了加入了flake.nix,Python软件包和项目依赖管理工具长期是个大问题。对于开发侧来说uv已经占据了主流——当然,在用户侧,则是conda,它和本篇没有太大关系——几年前我还在使用poetry,但uv确实太快了。从今年开始我已经完全切换到了uv下面。

语言服务器协议(lsp)实现方面, basedpyright的出现,让用户可以使用一些Pylance才有的功能,不过我个人不是很在乎。还有一个要特别提的点, PyPI里的Pyright for Python和微软官方并没有关系,可以通过 pip install pyright[nodejs] 实现和basedpyright一样的安装效果。

使用 flake.nix 有效管理项目开发环境

对于我的工作流,开发环境里最主要的就是编辑器和LSP服务器两个大项,当然,对于emacs编辑器,下面还有不少插件;对于lsp服务器,则不同项目的配置可能会不一样,尤其是不同语言的项目。我面临的最大问题就是这两个的版本问题。作用终端用户,我的希望是软件开发环境的变动是可控的,不会随着操作系统的升级而有变化。这种需求下flake.nix确实是相当合适的解决方案。

而对于Python项目来说,另一个更重要的需求则是Python本身的版本。尽管uv提供了Python版本的管理。不过我的选择是让flake.nix来管理Python的版本,uv只管理依赖。这样不用每个项目都复制一次Python。

对于这类flake.nix来说,每次都从github或者其它项目上拉取显得不是很明智,我选择在本地克隆出一个git仓库,然后把flake.nix中的nixpkgs.url指向这个本地的git仓库。同样的,多系统支持也不是必须的,这样直接指定 system = "x86_64-linux"; 也可以简化问题。

这样一个可以工作的flake.nix模板大致如下:

{
  description = "example flake.nix file";

  inputs = {
    nixpkgs.url = "git+file:///locale/repos/nixpkgs";
  };

  outputs = { self, nixpkgs }: let
    system = "x86_64-linux";
    pkgs = import nixpkgs {
      inherit system;
      config = {
        allowUnfree = true;
        # cudaSupport = false;
      };
    };

    emacsWithPackages = (pkgs.emacsPackagesFor pkgs.emacs30-pgtk).emacsWithPackages (epkgs: [
      epkgs.gcmh
      epkgs.yaml-mode

      epkgs.nix-mode
      epkgs.nixfmt
      epkgs.company
      epkgs.lsp-mode
      epkgs.lsp-ui
      epkgs.lsp-pyright
      epkgs.consult-lsp
    ]);
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        stdenv.cc.cc.lib

        # Python with basic packages
        (python312.withPackages (ps: with ps; [
          uv
        ]))

        emacsWithPackages
        basedpyright
        # # CUDA dependencies
        # cudatoolkit
        # cudnn

        python312
        git
        # Additional libraries
        zlib
        zstd.out
      ];

      shellHook = builtins.readFile ./shell_hook.sh;
      LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.zlib}/lib;${pkgs.zstd.out}/lib"; # Ensure libstdc++ is in the library path
      UV_LINK_MODE = "copy";
      UV_PYTHON_PREFERENCE = "only-system";
      UV_PYTHON = "${pkgs.python312}";
    };
  };
}

对于不是自己的项目来说,一个也许比较好的实践是在克隆了项目源码后,创建一个本地开发分支,然后依照自己的需求来创建或更改flake.nix。

其它如 emacs 和 lsp/pyright 配置细节

为了更好的控制开发环境,我还把 emacs 的初始化配置 init.el 也包含在了项目源码中,准确地说是本地开发分支。当然,如果发布的话,需要专门的发布分支,与编辑器等本地开发环境有关系的问题不应该包含在发布分支的代码中。

lsp本身还有一些配置,像比较重要的打开或新建文件时自动启动或者重启lsp,设定代码根目录变量这些都可以写在 init.el 中,然后通过 -l 命令行,指定路径参数进行加载。

basedpyright/pyright 的配置可以直接写在 pyproject.toml 中, 我唯一的建议是 typeCheckingMode 一定要改一下, strict 模式几乎是反人性,不可能有项目完全遵守的。

[tool.basedpyright]
executionEnvironments = [
  { root = "src" },
]
typeCheckingMode = "standard"

当然,因为 pyproject.toml 这个文件肯定会放到发布分支中,也许不应当建议在公开的项目中把 lsp 相关的配置写到这个文件里。