Python: Difference between revisions

From NixOS Wiki
Gdforj (talk | contribs)
Start improving the Nix+Python wiki guide.
Gdforj (talk | contribs)
Overhaul the Python page to make it more up to date and more accessible to Python users who have never used Nix.
Line 1: Line 1:


== Use a Python environment with Nix ==
== Python environments with Nix ==
Python environment allow you to run Python code with the <code>python</code> command. It may also have Python packages installed as well.
Python environment allow you to run Python code with the <code>python</code> command. It may also have Python packages installed as well.


Line 6: Line 6:


There are many ways to create a Python environment with Nix.
There are many ways to create a Python environment with Nix.
=== Using regular Python virtual environment ===
It is possible to use Nix to create a Python virtual environment for your project. You can then use the Python virtual environment as usual.
To create a Python virtual environment:<syntaxhighlight lang="console">
$ nix-shell -p python3 --command "python -m venv .venv --copies"
</syntaxhighlight>You can then activate and use the Python virtual environment as usual and install dependencies with <code>pip</code> and similar.
==== On NixOS ====
This method may not work on NixOS, as installing packages with <code>pip</code> that need to compile code or use C libraries will fail due to not finding dependencies in the expected places.
There are multiple ways to make it work:
* Use [https://github.com/GuillaumeDesforges/fix-python/ fix-python], this is most suited for beginners.
* Create a FHS user env with <code>buildFHSUserEnv</code>.
* Setup <code>nix-ld</code> in your NixOS configuration.


=== Using the Nixpkgs Python infrastructure ===
=== Using the Nixpkgs Python infrastructure ===
Line 70: Line 88:
   ];
   ];
}
}
</syntaxhighlight>Given the file above is named <code>toolz.nix</code> and is the same directory as the previous <code>shell.nix</code> , you can edit <code>shell.nix</code> to use the package <code>toolz</code> above like so: <syntaxhighlight lang="nix" line="1">
</syntaxhighlight>The tool [https://github.com/nix-community/pip2nix pip2nix] can help you generate such files.
 
Given the file above is named <code>toolz.nix</code> and is the same directory as the previous <code>shell.nix</code> , you can edit <code>shell.nix</code> to use the package <code>toolz</code> above like so: <syntaxhighlight lang="nix" line="1">
# shell.nix
# shell.nix
let
let
Line 87: Line 107:


Next time you enter the shell specified by this file, Nix will build and include the Python package you have written.
Next time you enter the shell specified by this file, Nix will build and include the Python package you have written.
=== Using micromamba ===
Install the <code>micromamba</code> package to create environments and install packages as [https://github.com/mamba-org/mamba#micromamba documented by micromamba].
To activate an environment you will need a [https://nixos.org/manual/nixpkgs/stable/#sec-fhs-environments FHS environment] e.g.:
<syntaxhighlight lang="console">
$ nix-shell -E 'with import <nixpkgs> {}; (pkgs.buildFHSUserEnv { name = "fhs"; }).env'
$ eval "$(micromamba shell hook -s bash)"
$ micromamba activate my-environment
$ python
>>> import numpy as np
</syntaxhighlight>
Eventually you'll probably want to put this in a shell.nix so you won't have to type all that stuff every time e.g.:
<syntaxhighlight lang="shell">
{ pkgs ? import <nixpkgs> {}}:
let
  fhs = pkgs.buildFHSUserEnv {
    name = "my-fhs-environment";
    targetPkgs = _: [
      pkgs.micromamba
    ];
    profile = ''
      set -e
      eval "$(micromamba shell hook --shell=posix)"
      export MAMBA_ROOT_PREFIX=${builtins.getEnv "PWD"}/.mamba
      micromamba create -q -n my-mamba-environment
      micromamba activate my-mamba-environment
      micromamba install --yes -f conda-requirements.txt -c conda-forge
      set +e
    '';
  };
in fhs.env
</syntaxhighlight>
=== Using conda ===
Install the package <code>conda</code> and run
<syntaxhighlight lang="console">
$ conda-shell
$ conda-install
$ conda env update --file environment.yml
</syntaxhighlight>
==== Imperative use ====
It is also possible to use <code>conda-install</code> directly. On first use, run:
<syntaxhighlight lang="console">
$ conda-shell
$ conda-install
</syntaxhighlight>
to set up conda in <code>~/.conda</code>


== Package a Python application ==
== Package a Python application ==
Line 166: Line 240:
</syntaxhighlight>
</syntaxhighlight>


=== Python virtual environment ===
== Nixpkgs Python contribution guidelines ==


Starting from Python 3 virtual environment is natively supported.
=== Libraries ===
The Python 3 venv approach has the benefit of forcing you to choose a specific version of the Python 3 interpreter that should be used to create the virtual environment. This avoids any confusion as to which Python installation the new environment is based on.
 
Recommended usage:
* Python 3.3-3.4 (old): the recommended way to create a virtual environment was to use the pyvenv command-line tool that also comes included with your Python 3 installation by default.
* Python 3.6+: <code>python3 -m venv</code> is the way to go.
 
Put your packages in a requirements.txt:
<syntaxhighlight lang=text>
pandas
requests
</syntaxhighlight>
 
Then setup the virtualenv:
<syntaxhighlight lang="shell">
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
</syntaxhighlight>
 
Installing packages with <code>pip</code> that need to compile code or use C libraries will sometimes fail due to not finding dependencies in the expected places. In that case you can use <code>buildFHSUserEnv</code> to make yourself a sandbox that appears like a more typical Linux install (or you can also certainly use <code>nix-ld</code> to turn your whole system into a more standard Linux distribution). For example if you were working with machine learning code you could use:
 
<syntaxhighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
(pkgs.buildFHSUserEnv {
  name = "pipzone";
  targetPkgs = pkgs: (with pkgs; [
    python39
    python39Packages.pip
    python39Packages.virtualenv
    cudaPackages.cudatoolkit
  ]);
  runScript = "bash";
}).env
</syntaxhighlight>
 
In <code>pip-shell.nix</code>, and enter the environment with:
 
<syntaxhighlight lang="shell">
nix-shell pip-shell.nix
virtualenv venv
source venv/bin/activate
</syntaxhighlight>
 
==== Virtualenv without nix on NixOS ====
Another option is to follow the [https://gist.github.com/GuillaumeDesforges/7d66cf0f63038724acf06f17331c9280 fix-python gist] to setup a virtualenv without explicitly entering a nix shell.
 
=== Emulating virtualenv with nix-shell ===
In some cases virtualenv fails to install a library because it requires patching on NixOS (example 1, example 2, general issue). In this cases it is better to replace those libraries with ones from Nix.
 
Let's say, that nanomsg library fails to install in virtualenv. Then write a <code>shell.nix</code> file:
 
<syntaxhighlight lang="nix">
let
  pkgs = import <nixpkgs> {};
  nanomsg-py = ...build expression for this python library...;
in pkgs.mkShell {
  buildInputs = [
    pkgs.python3
    pkgs.python3.pkgs.requests
    nanomsg-py
  ];
  shellHook = ''
    # Tells pip to put packages into $PIP_PREFIX instead of the usual locations.
    # See https://pip.pypa.io/en/stable/user_guide/#environment-variables.
    export PIP_PREFIX=$(pwd)/_build/pip_packages
    export PYTHONPATH="$PIP_PREFIX/${pkgs.python3.sitePackages}:$PYTHONPATH"
    export PATH="$PIP_PREFIX/bin:$PATH"
    unset SOURCE_DATE_EPOCH
  '';
}
</syntaxhighlight>


After entering the environment with `nix-shell`, you can install new python libraries with dump `pip install`, but nanomsg will be detected as installed.
According to the [https://nixos.org/nixpkgs/manual/#contributing-guidelines official guidelines] for Python, new package expressions for libraries should be placed in <syntaxhighlight lang="bash" inline>pkgs/development/python-modules/<name>/default.nix</syntaxhighlight>.


Discussion and consequences of this approach are in PR https://github.com/NixOS/nixpkgs/pull/55265.
Those expressions are then referenced from <code>pkgs/top-level/python-packages.nix</code> like in this example:
 
=== micromamba ===
 
Install the <code>micromamba</code> package.
You can create environments and install packages as [https://github.com/mamba-org/mamba#micromamba documented by micromamba] e.g.
<syntaxhighlight lang="shell">
micromamba create -n my-environment python=3.9 numpy=1.23.0 -c conda-forge
</syntaxhighlight>
 
To activate an environment you will need a [https://nixos.org/manual/nixpkgs/stable/#sec-fhs-environments FHS environment] e.g.:
<syntaxhighlight lang="console">
$ nix-shell -E 'with import <nixpkgs> {}; (pkgs.buildFHSUserEnv { name = "fhs"; }).env'
$ eval "$(micromamba shell hook -s bash)"
$ micromamba activate my-environment
$ python
>>> import numpy as np
</syntaxhighlight>
 
Eventually you'll probably want to put this in a shell.nix so you won't have to type all that stuff every time e.g.:
<syntaxhighlight lang="shell">
{ pkgs ? import <nixpkgs> {}}:
let
  fhs = pkgs.buildFHSUserEnv {
    name = "my-fhs-environment";
 
    targetPkgs = _: [
      pkgs.micromamba
    ];
 
    profile = ''
      set -e
      eval "$(micromamba shell hook --shell=posix)"
      export MAMBA_ROOT_PREFIX=${builtins.getEnv "PWD"}/.mamba
      micromamba create -q -n my-mamba-environment
      micromamba activate my-mamba-environment
      micromamba install --yes -f conda-requirements.txt -c conda-forge
      set +e
    '';
  };
in fhs.env
</syntaxhighlight>
 
 
=== conda ===
 
Install the package <code>conda</code> and run
<syntaxhighlight lang="shell">
conda-shell
conda-install
conda env update --file environment.yml
</syntaxhighlight>
 
==== Imperative use ====
 
It is also possible to use <code>conda-install</code> directly.
On first use, run
<syntaxhighlight lang="shell">
conda-shell
conda-install
</syntaxhighlight>
to set up conda in <code>~/.conda</code>
 
=== pip2nix ===
[https://github.com/nix-community/pip2nix pip2nix] generate nix expressions for Python packages.
 
Also see the [https://github.com/nix-community/pypi2nix pypi2nix]-project (abandoned in 2019).
 
== Contribution guidelines ==
 
=== Libraries ===
 
According to the [https://nixos.org/nixpkgs/manual/#contributing-guidelines official guidelines] for python new package expressions for libraries should be placed in <syntaxhighlight lang="bash" inline>pkgs/development/python-modules/<name>/default.nix</syntaxhighlight>. Those expressions are then referenced from <code>pkgs/top-level/python-packages.nix</code> like in this example:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
{
   aenum = callPackage ../development/python-modules/aenum { };
   aenum = callPackage ../development/python-modules/aenum { };
}
</syntaxhighlight>
</syntaxhighlight>
The reasoning behind this is the large size of <code>pkgs/top-level/python-packages.nix</code>.


=== Applications ===
=== Applications ===


Python applications instead should be referenced directly from <code>pkgs/top-level/all-packages.nix</code>.
Applications meant to be executed should be referenced directly from <code>pkgs/top-level/all-packages.nix</code>.


The expression should take <code>pythonPackages</code> as one of the arguments, which guarantees that packages belong to the same set. For example:
Other Python packages used in the Python package of the application should be taken from the <code>callPackage</code> argument <code>pythonPackages</code> , which guarantees that they belong to the same "pythonPackage" set. For example:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix" line="1">
{ lib
{ lib
, pythonPackages
, pythonPackages
}:
}:
with pythonPackages;


buildPythonApplication rec {
buildPythonApplication rec {
# ...
  # ...
  propagatedBuildInputs = [
    pythonPackages.numpy
  ];
  # ...
}
</syntaxhighlight>
</syntaxhighlight>
== Special Modules ==
== Special Modules ==
Line 369: Line 299:


As of the time of this writing; these optimizations cause Python wheels to be non-reproducible and increase install times for the derivation. For a more detailed overview of the trials and tabulations of discovering the performance regression; see [https://discourse.nixos.org/t/why-is-the-nix-compiled-python-slower/18717 Why is the nix-compiled Python slower?] thread on the nix forums.
As of the time of this writing; these optimizations cause Python wheels to be non-reproducible and increase install times for the derivation. For a more detailed overview of the trials and tabulations of discovering the performance regression; see [https://discourse.nixos.org/t/why-is-the-nix-compiled-python-slower/18717 Why is the nix-compiled Python slower?] thread on the nix forums.
=== Regression ===
=== Regression ===
With the <code>nixpkgs</code> version of Python you can expect anywhere from a 30-40% regression on synthetic benchmarks. For example:  
With the <code>nixpkgs</code> version of Python you can expect anywhere from a 30-40% regression on synthetic benchmarks. For example:  
Line 383: Line 311:


However, synthetic benchmarks are not a reflection of a real-world use case. In most situations, the performance difference between optimized & non-optimized interpreters is minimal. For example; using <code>pylint</code> with a significant number of custom linters to go scan a very large Python codebase (>6000 files) resulted in only a 5.5% difference, instead of 40%. Other workflows that were not performance sensitive saw no impact to their run times.
However, synthetic benchmarks are not a reflection of a real-world use case. In most situations, the performance difference between optimized & non-optimized interpreters is minimal. For example; using <code>pylint</code> with a significant number of custom linters to go scan a very large Python codebase (>6000 files) resulted in only a 5.5% difference, instead of 40%. Other workflows that were not performance sensitive saw no impact to their run times.
=== Possible Optimizations ===
=== Possible Optimizations ===
If you run code that heavily depends on Python performance (data science, machine learning), and you want to have the most performant Python interpreter possible, here are some possible things you can do:
If you run code that heavily depends on Python performance (data science, machine learning), and you want to have the most performant Python interpreter possible, here are some possible things you can do:
Line 408: Line 334:


* [https://nixos.org/manual/nixpkgs/unstable/#python "Python" in Nixpkgs Manual]
* [https://nixos.org/manual/nixpkgs/unstable/#python "Python" in Nixpkgs Manual]
* [[Packaging/Python]]
* [https://github.com/cachix/nixpkgs-python Nixpgs-python: A repo providing every version of python as flake ]
[[Category:Languages]]
[[Category:Languages]]

Revision as of 11:58, 2 April 2024

Python environments with Nix

Python environment allow you to run Python code with the python command. It may also have Python packages installed as well.

Nix allows to create many independent Python environment, much like venv.

There are many ways to create a Python environment with Nix.

Using regular Python virtual environment

It is possible to use Nix to create a Python virtual environment for your project. You can then use the Python virtual environment as usual.

To create a Python virtual environment:

$ nix-shell -p python3 --command "python -m venv .venv --copies"

You can then activate and use the Python virtual environment as usual and install dependencies with pip and similar.

On NixOS

This method may not work on NixOS, as installing packages with pip that need to compile code or use C libraries will fail due to not finding dependencies in the expected places.

There are multiple ways to make it work:

  • Use fix-python, this is most suited for beginners.
  • Create a FHS user env with buildFHSUserEnv.
  • Setup nix-ld in your NixOS configuration.

Using the Nixpkgs Python infrastructure

Nixpkgs has the few last Python versions packaged, as well as a consequent set of Python packages packaged that you can use to quickly create a Python environment.

Create a file shell.nix in the project directory, with the following template:

# shell.nix
let
  pkgs = import <nixpkgs> {};
in pkgs.mkShell {
  packages = [
    (pkgs.python3.withPackages (python-pkgs: [
      # select Python packages here
      python-pkgs.pandas
      python-pkgs.requests
    ]))
  ];
}

This example will make Nix create a Python environment with Python packages "pandas" and "requests". You can remove them, or add more.

You can find Python packages that are available in Nixpkgs using search.nixos.org. For instance, type a Python package name (for example numpy) in the search bar and click on the search button on the right. Then on the left in the "Package sets" section click on "python311Packages" to list only Python packages. for Python (version 3.11).

Note that in the snippet above, on line 8 and 9, each package to install is in the form python-pkgs.<name>. The package name must correspond to the one found in search.nixos.org as explained above. To understand why it is prefixed by python-pkgs, read the Nix language basics.

Then, once you have picked the Python packages you want, run nix-shell (or nix develop -f shell.nix) to make Nix create the Python environment and enter it. If it succeeds, Python should be available in your PATH, try using python --version at this point.

As a word of warning, using pkgs = import <nixpkgs> {}; as in the snippet above (line 3) will result in a non-reproducible environment. Use Nixpkgs pinning or Nix Flakes to make your environment reproducible.

Using a Python packages not in Nixpkgs

Python packages in Nixpkgs are created and updated by Nixpkgs maintainers. Although the community invests a great effort to keep a complete and up-to-date package set, it isn't uncommon for packages you want to be missing, out of date or broken. To use your own packages in a Nix environment, you may package it yourself.

The following is a high-level overview. For a complete explanation, see Developing with Python in the Nixpkgs Manual.

Generally, you may create a file that looks like this:

# toolz.nix
{ lib
, buildPythonPackage
, fetchPypi
, setuptools
, wheel
}:

buildPythonPackage rec {
  pname = "toolz";
  version = "0.10.0";

  src = fetchPypi {
    inherit pname version;
    hash = "sha256-CP3V73yWSArRHBLUct4hrNMjWZlvaaUlkpm1QP66RWA=";
  };

  # do not run tests
  doCheck = false;

  # specific to buildPythonPackage, see its reference
  pyproject = true;
  build-system = [
    setuptools
    wheel
  ];
}

The tool pip2nix can help you generate such files. Given the file above is named toolz.nix and is the same directory as the previous shell.nix , you can edit shell.nix to use the package toolz above like so:

# shell.nix
let
  pkgs = import <nixpkgs> {};
in pkgs.mkShell {
  packages = [
    (pkgs.python3.withPackages (python-pkgs: [
      # select Python packages here
      python-pkgs.pandas
      python-pkgs.requests
      (pkgs.callPackage ./toolz.nix)
    ]))
  ];
}

Note that the parenthesis (line 10) are required.

Next time you enter the shell specified by this file, Nix will build and include the Python package you have written.

Using micromamba

Install the micromamba package to create environments and install packages as documented by micromamba.

To activate an environment you will need a FHS environment e.g.:

$ nix-shell -E 'with import <nixpkgs> {}; (pkgs.buildFHSUserEnv { name = "fhs"; }).env'
$ eval "$(micromamba shell hook -s bash)"
$ micromamba activate my-environment
$ python
>>> import numpy as np

Eventually you'll probably want to put this in a shell.nix so you won't have to type all that stuff every time e.g.:

{ pkgs ? import <nixpkgs> {}}:
let
  fhs = pkgs.buildFHSUserEnv {
    name = "my-fhs-environment";

    targetPkgs = _: [
      pkgs.micromamba
    ];

    profile = ''
      set -e
      eval "$(micromamba shell hook --shell=posix)"
      export MAMBA_ROOT_PREFIX=${builtins.getEnv "PWD"}/.mamba
      micromamba create -q -n my-mamba-environment
      micromamba activate my-mamba-environment
      micromamba install --yes -f conda-requirements.txt -c conda-forge
      set +e
    '';
  };
in fhs.env

Using conda

Install the package conda and run

$ conda-shell
$ conda-install
$ conda env update --file environment.yml

Imperative use

It is also possible to use conda-install directly. On first use, run:

$ conda-shell
$ conda-install

to set up conda in ~/.conda

Package a Python application

It is possible to use buildPythonApplication to package python applications. As explained in the nixpkgs manual, it uses the widely used `setup.py` file in order to package properly the application. We now show how to package a simple python application: a basic flask web server.

First, we write the python code, say in a file web_interface.py. Here we create a basic flask web server;

#!/usr/bin/env python

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080)

Then, we create the setup.py file, which basically explains which are the executables:

#!/usr/bin/env python

from setuptools import setup, find_packages

setup(name='demo-flask-vuejs-rest',
      version='1.0',
      # Modules to import from other scripts:
      packages=find_packages(),
      # Executables
      scripts=["web_interface.py"],
     )

Finally, our nix derivation is now trivial: the file derivation.nix just needs to provide the python packages (here flask):

{ lib, python3Packages }:
with python3Packages;
buildPythonApplication {
  pname = "demo-flask-vuejs-rest";
  version = "1.0";

  propagatedBuildInputs = [ flask ];

  src = ./.;
}

and we can now load this derivation from our file default.nix:

{ pkgs ? import <nixpkgs> {} }:
pkgs.callPackage ./derivation.nix {}

We can now build with:

$ nix-build
[...]
$ ./result/bin/web_interface.py 
 * Serving Flask app ".web_interface" (lazy loading)
 [...]

or just enter a nix-shell, and directly execute your program or python if it's easier to develop:

$ nix-shell
[...]
[nix-shell]$ chmod +x web_interface.py
[nix-shell]$ ./web_interface.py 
 * Serving Flask app "web_interface" (lazy loading)
[...]

[nix-shell]$ python
Python 3.8.7 (default, Dec 21 2020, 17:18:55) 
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import flask
>>>

Nixpkgs Python contribution guidelines

Libraries

According to the official guidelines for Python, new package expressions for libraries should be placed in pkgs/development/python-modules/<name>/default.nix.

Those expressions are then referenced from pkgs/top-level/python-packages.nix like in this example:

  aenum = callPackage ../development/python-modules/aenum { };

Applications

Applications meant to be executed should be referenced directly from pkgs/top-level/all-packages.nix.

Other Python packages used in the Python package of the application should be taken from the callPackage argument pythonPackages , which guarantees that they belong to the same "pythonPackage" set. For example:

{ lib
, pythonPackages
}:

buildPythonApplication rec {
  # ...
  propagatedBuildInputs = [
    pythonPackages.numpy
  ];
  # ...
}

Special Modules

GNOME

gobject-introspection based python modules need some environment variables to work correctly. For standalone applications, wrapGAppsHook (see the relevant documentation) wraps the executable with the necessary variables. But this is not fit for development. In this case use a nix-shell with gobject-introspection and all the libraries you are using (gtk and so on) as buildInputs. For example:

$ nix-shell -p gobjectIntrospection gtk3 'python2.withPackages (ps: with ps; [ pygobject3 ])' --run "python -c \"import pygtkcompat; pygtkcompat.enable_gtk(version='3.0')\""

Or, if you want to use matplotlib interactively:

$ nix-shell -p gobject-introspection gtk3 'python36.withPackages(ps : with ps; [ matplotlib pygobject3 ipython ])'
$ ipython
In [1]: import matplotlib
In [2]: matplotlib.use('gtk3agg')
In [3]: import matplotlib.pyplot as plt
In [4]: plt.ion()
In [5]: plt.plot([1,3,2,4])

You can also set backend : GTK3Agg in your ~/.config/matplotlib/matplotlibrc file to avoid having to call matplotlib.use('gtk3agg').

Performance

The derivation of CPython that is available via nixpkgs only contains optimizations that do not harm reproducibility. Link-Time-Optimization (LTO) is only enabled on 64-bit Linux systems, while Profile Guided Optimization (PGO) is currently disabled. See Configuring Python 3.1.3. Performance options Additionally, when you compile something within nix-shell or a derivation; by default there are security hardening flags passed to the compiler which do have a small performance impact.

As of the time of this writing; these optimizations cause Python wheels to be non-reproducible and increase install times for the derivation. For a more detailed overview of the trials and tabulations of discovering the performance regression; see Why is the nix-compiled Python slower? thread on the nix forums.

Regression

With the nixpkgs version of Python you can expect anywhere from a 30-40% regression on synthetic benchmarks. For example:

## Ubuntu's Python 3.8
username:dir$ python3.8 -c "import timeit; print(timeit.Timer('for i in range(100): oct(i)', 'gc.enable()').repeat(5))"
[7.831622750498354, 7.82998560462147, 7.830805554986, 7.823807033710182, 7.84282516874373]

## nix-shell's Python 3.8
[nix-shell:~/src]$ python3.8 -c "import timeit; print(timeit.Timer('for i in range(100): oct(i)', 'gc.enable()').repeat(5))"
[10.431915327906609, 10.435049421153963, 10.449542525224388, 10.440207410603762, 10.431304694153368]

However, synthetic benchmarks are not a reflection of a real-world use case. In most situations, the performance difference between optimized & non-optimized interpreters is minimal. For example; using pylint with a significant number of custom linters to go scan a very large Python codebase (>6000 files) resulted in only a 5.5% difference, instead of 40%. Other workflows that were not performance sensitive saw no impact to their run times.

Possible Optimizations

If you run code that heavily depends on Python performance (data science, machine learning), and you want to have the most performant Python interpreter possible, here are some possible things you can do:

  • Enable the enableOptimizations flag for your Python derivation. Example Do note that this will cause you to compile Python the first time that you run it; which will take a few minutes.
  • Switch to a newer version of Python. In the example above, going from 3.8 to 3.10 yielded an average 7.5% performance improvement; but this is only a single benchmark. Switching versions most likely won't make all your code 7.5% faster.
  • Disable hardening, although this only yields a small performance boost; and it has impacts beyond Python code. Hardening in Nixpkgs

Ultimately, it is up to your use case to determine if you need an optimized version of the Python interpreter. We encourage you to benchmark and test your code to determine if this is something that would benefit you.

Troubleshooting

My module cannot be imported

If you are unable to do `import yourmodule` there are a number of reasons that could explain that.

First, make sure that you installed/added your module to python. Typically you would use something like (python3.withPackages (ps: with ps; [ yourmodule ])) in the list of installed applications.

It is also still possible (e.g. when using nix-shell) that you aren't using the python interpreter you want because another package provides its own python3.withPackages in buildInputs, for example, yosys. In this case, you should either include that package (or all needed packages) in your withPackages list to only have a single Python interpreter. Or you can change the order of your packages, such that the python3.withPackages comes first, and becomes the Python interpreter that you get.

If you packaged yourself your application, make sure to use buildPythonPackage and **not** buildPythonApplication or stdenv.mkDerivation. The reason is that python3.withPackages filters the packages to check that they are built using the appropriate python interpreter: this is done by verifying that the derivation has a pythonModule attribute and only buildPythonPackage sets this value (passthru here) thanks to, notably passthru = { pythonModule = python; }. If you used stdenv.mkDerivation then you can maybe set this value manually, but it's safer to simply use buildPythonPackage {format = "other"; … your derivation …} instead of mkDerivation.

See also