Bug report
Bug description:
Description
The built-in functools.reduce function (implemented in _functools module) does not accept keyword arguments for any of its parameters (function, iterable, initializer). This behavior is inconsistent with other functions in the functools module (like partial, lru_cache, etc.) and limits its usability in functional programming patterns, especially with functools.partial.
Steps to Reproduce
- Import
reduce and partial from functools, and import an operator (e.g., operator.mul).
- Attempt to create a partial function for reduction using keyword arguments.
- Observe the
TypeError.
Code Example:
from functools import reduce, partial
import operator
result1 = reduce(operator.mul, [2, 3, 5], 1) # 30, without problem
# Attempt to create a product reducer with initializer=1 using partial
product_int = partial(reduce, operator.mul, initial=1)
result2 = product_int([2, 3, 5]) # Expected: 30
Expected Behavior
The reduce function should accept keyword arguments for its parameters (at least for initializer), allowing constructions like:
product_int = partial(reduce, operator.mul, initial=1)
result = product_int([2, 3, 5]) # Returns 30
This would align with the flexibility offered by other Python functions and improve compatibility with functools.partial.
Actual Behavior
A TypeError is raised:
TypeError: reduce() takes no keyword arguments
Environment
- Python Version: CPython 3.12.7
- Operating System: Windows 10
- Note: The issue stems from the C implementation of
reduce in _functools, which does not support keyword arguments. The pure-Python fallback implementation in functools.py does handle keyword arguments but is overridden by the C version when available.
Root Cause Analysis
The C implementation of reduce (in _functools module) is optimized for performance and does not define a signature that accepts keyword arguments. This is a common optimization in C-extensions but here it limits functionality. The pure-Python version in functools.py (used as fallback if _functools is not available) does support keyword arguments, as seen in its source:
def reduce(function, sequence, initial=_initial_missing):
# ... (implementation that can handle keyword arguments)
However, since the C implementation is almost always present (as _functools is a built-in module), the pure-Python version is not used.
Possible Solutions
- Modify the C implementation to support keyword arguments for its parameters, especially initial. This would involve using a calling convention that supports keywords (e.g.,
METH_FASTCALL | METH_KEYWORDS) and updating the argument parsing logic to use PyArg_ParseTupleAndKeywords (or equivalent), while maintaining full backward compatibility with existing code that uses positional arguments.
- Make the
initial parameter also a keyword argument in the C version. This would be a simpler change and directly enable the desired use case with partial.
- Ensure consistency between the C and pure-Python implementations. The pure-Python version should be the reference for behavior.
Additional Context
- This limitation prevents elegant functional composition using
partial, which is a common paradigm in functional programming.
- The workaround is to use a lambda or helper function, which is less readable and less efficient:
product_int = lambda seq: reduce(operator.mul, seq, 1)
- The issue is similar to historical problems in Cython-compiled functions (see Cython issue #2881), where functions by default did not accept keyword arguments for performance reasons. This was solvable via the
always_allow_keywords directive.
- Other functions in
functools (like partial, lru_cache) extensively use and support keyword arguments, making reduce an outlier.
Linked PRs/Issues
- None found yet, but this might be related to a broader design choice about performance vs. flexibility in C-implemented functions.
Proposed Patch
A patch would need to modify the C code for _functools_reduce (likely in Modules/_functools.c) to:
- Change its calling convention to one that supports keyword arguments (e.g.,
METH_FASTCALL | METH_KEYWORDS).
- Parse arguments using
PyArg_ParseTupleAndKeywords (or a similar API) to accept function, sequence, and initial either by position or by keyword.
- Crucially, maintain 100% backward compatibility with the existing positional argument calling style.
Alternatively, a simpler approach is to make only the initial parameter accessible via keyword, which might be sufficient for the primary use case.
CPython versions tested on:
3.12
Operating systems tested on:
Windows
Bug report
Bug description:
Description
The built-in
functools.reducefunction (implemented in_functoolsmodule) does not accept keyword arguments for any of its parameters (function,iterable,initializer). This behavior is inconsistent with other functions in thefunctoolsmodule (likepartial,lru_cache, etc.) and limits its usability in functional programming patterns, especially withfunctools.partial.Steps to Reproduce
reduceandpartialfromfunctools, and import an operator (e.g.,operator.mul).TypeError.Code Example:
Expected Behavior
The
reducefunction should accept keyword arguments for its parameters (at least forinitializer), allowing constructions like:This would align with the flexibility offered by other Python functions and improve compatibility with
functools.partial.Actual Behavior
A
TypeErroris raised:Environment
reducein_functools, which does not support keyword arguments. The pure-Python fallback implementation infunctools.pydoes handle keyword arguments but is overridden by the C version when available.Root Cause Analysis
The C implementation of
reduce(in_functoolsmodule) is optimized for performance and does not define a signature that accepts keyword arguments. This is a common optimization in C-extensions but here it limits functionality. The pure-Python version infunctools.py(used as fallback if_functoolsis not available) does support keyword arguments, as seen in its source:However, since the C implementation is almost always present (as
_functoolsis a built-in module), the pure-Python version is not used.Possible Solutions
METH_FASTCALL | METH_KEYWORDS) and updating the argument parsing logic to usePyArg_ParseTupleAndKeywords(or equivalent), while maintaining full backward compatibility with existing code that uses positional arguments.initialparameter also a keyword argument in the C version. This would be a simpler change and directly enable the desired use case withpartial.Additional Context
partial, which is a common paradigm in functional programming.always_allow_keywordsdirective.functools(likepartial,lru_cache) extensively use and support keyword arguments, makingreducean outlier.Linked PRs/Issues
Proposed Patch
A patch would need to modify the C code for
_functools_reduce(likely inModules/_functools.c) to:METH_FASTCALL | METH_KEYWORDS).PyArg_ParseTupleAndKeywords(or a similar API) to acceptfunction,sequence, andinitialeither by position or by keyword.Alternatively, a simpler approach is to make only the
initialparameter accessible via keyword, which might be sufficient for the primary use case.CPython versions tested on:
3.12
Operating systems tested on:
Windows