-
-
Notifications
You must be signed in to change notification settings - Fork 33.1k
Description
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
andpartial
fromfunctools
, 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 infunctools.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 usePyArg_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 withpartial
. - 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
(likepartial
,lru_cache
) extensively use and support keyword arguments, makingreduce
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 acceptfunction
,sequence
, andinitial
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