Python's standard library is vast, and my intuition tells that there must be a way in it to accomplish this, but I just can't figure it out. This is purely for curiosity and learning purposes:
I have two simple functions:
def increment(x):
return x + 1
def double(x):
return x * 2
and I want to compose them into a new function double_and_increment
. I could of course simply do that as such:
double_and_increment = lambda x: increment(double(x))
but I could also do it in a more convoluted but perhaps more "ergonomically scalable" way:
import functools
double_and_increment = functools.partial(functools.reduce, lambda acc, f: f(acc), [double, increment])
Both of the above work fine:
>>> double_and_increment(1)
3
Now, the question is, is there tooling in the standard library that would allow achieving the composition without any user-defined lambdas, regular functions, or classes.
The first intuition is to replace the lambda acc, f: f(acc)
definition in the functools.reduce
call with operator.call
, but that unfortunately takes the arguments in the reverse order:
>>> (lambda acc, f: f(acc))(1, str) # What we want to replace.
>>> '1'
>>> import operator
>>> operator.call(str, 1) # Incorrect argument order.
>>> '1'
I have a hunch that using functools.reduce
is still the way to accomplish the composition, but for the life of me I can't figure out a way to get rid of the user-defined lambda.
Few out-of-the-box methods that got me close:
import functools, operator
# Curried form, can't figure out how to uncurry.
functools.partial(operator.methodcaller, '__call__')(1)(str)
# The arguments needs to be in the middle of the expression, which does not work.
operator.call(*reversed(operator.attrgetter('args')(functools.partial(functools.partial, operator.call)(1, str))))
Have looked through all the existing questions, but they are completely different and rely on using user-defined functions and/or lambdas.
As mentioned in the other answer of mine I don't agree that the test suite discovered by @AKX should be considered as part of the standard library per the OP's rules.
As it turns out, while researching for an existing function to modify for my other answer, I found that there is this helper function _int_to_enum
in the signal
module that perfectly implements operator.call
for a callable with a single argument, but with parameters reversed, exactly how the OP wants it, and is available since Python 3.5:
def _int_to_enum(value, enum_klass):
"""Convert a numeric value to an IntEnum member.
If it's not a known member, return the numeric value itself.
"""
try:
return enum_klass(value)
except ValueError:
return value
So we can simply repurpose/abuse it:
from signal import _int_to_enum as rcall
from functools import reduce, partial
def increment(x):
return x + 1
def double(x):
return x * 2
double_and_increment = partial(reduce, rcall, [double, increment])
print(double_and_increment(1))
This outputs:
3
Demo: here