`#[pyfunction]` gets `METH_STATIC` since 0.28, which makes calls slower on CPython 3.11+
Bug Description
Since PyO3 0.28, every module-level #[pyfunction] is created with the METH_STATIC flag. A function with one parameter has ml_flags 0xa2 (METH_FASTCALL | METH_KEYWORDS | METH_STATIC). With PyO3 0.27.2 it has 0x82.
The flag has two effects:
function.__self__isNone. With 0.27.2 it is the module, as it is for CPython's own builtin functions such asmath.sqrt.- On CPython 3.11 and newer, a call of a function that has parameters and no
**kwargsis about 2 to 4 ns slower. For a function with an empty body this is 10 to 21%. Functions without parameters and functions with**kwargsare not slower. CPython 3.9 and 3.10 are not affected.
I found this while benchmarking fastcrc after upgrading PyO3 from 0.26 to 0.29.2.
Steps to Reproduce
A reproduction is at https://github.com/overcat/pyo3-meth-static-repro. It defines pairs of functions with empty bodies. One function of each pair is a plain #[pyfunction]. The other one uses pass_module, which does not get the flag, and is the control.
git clone https://github.com/overcat/pyo3-meth-static-repro
cd pyo3-meth-static-repro
python -m venv .venv
.venv/bin/pip install .
.venv/bin/python repro.pyOutput with CPython 3.14.6 and PyO3 0.29.2:
plain #[pyfunction] #[pyfunction(pass_module)]
ml_flags __self__ ns/call ml_flags __self__ ns/call difference
no parameters 0x0024 None 13.1 0x0004 module 13.0 +0.1 ns +1%
one parameter 0x00a2 None 19.6 0x0082 module 16.4 +3.3 ns +20%
**kwargs 0x0023 None 16.8 0x0003 module 16.7 +0.1 ns +1%
0x0020 = METH_STATICWith PyO3 0.27.2, both columns show the same flags, __self__ is the module in both, and the differences are 0.0 ns.
Your operating system and version
Linux Mint 22.3 (kernel 7.0.0-31-generic, x86-64)
Your Python version (python --version)
Python 3.14.6
Your Rust version (rustc --version)
rustc 1.98.0 (88d9e12ae 2026-08-18)
Your PyO3 version
0.29.2 (also 0.28.2 and main at cb456da1; 0.27.2 is not affected)
How did you install python? Did you use a virtualenv?
mise (a precompiled python-build-standalone build), in a virtualenv created with "python -m venv"
Additional Info
This was introduced by #5581. The README of the reproduction explains the cause in PyO3 and in CPython. It also has the results for several PyO3 and CPython versions, the instruction counts and a workaround.
There is a candidate fix with a test: https://github.com/overcat/pyo3/commit/b3d1fd3277ee15d25943593aaab2cf17d20e7198. If you agree with the approach, I can turn it into a PR.
Source: PyO3/pyo3