Python for Everyone!
Oregon Curriculum Network
The function type (types.FunctionType) is not one to multiply by others of that type. If f and g are both functions, "h = f * g" does not initially make any sense.
However it does make sense to multiply two functions, giving a function result, when "to multiply" means "to compose" such that
The Composable class below eats a function to create an instance of itself that both multiplies (composes with other functions) and powers (composes with itself).
pow(Composable(f), 5) is equivalent to lambda x: f(f(f(f(f(x))))).
Calling the instance with an argument returns the same result the original function would have: f(x) == Composable(f)(x).
In [1]:
import types # <-- to get FunctionType
class Composable:
"""
Composable swallows a function, which may still be called, by
calling the instance instead. Used as a decorator, the Composable
class enables composition of functions by means of multiplying
and powering their corresponding Composable instances.
"""
def __init__(self, func):
self.func = func # eat a callable
def __call__(self, x):
return self.func(x) # still a callable
def __mul__(self, other):
"""
multiply two Composables i.e. (f * g)(x) == f(g(x))
g might might a function. OK if f is Composable.
"""
if isinstance(other, types.FunctionType): # OK if target is a function
other = Composable(other)
if not isinstance(other, Composable): # by this point, other must be one
raise TypeError
return Composable(lambda x: self.func(other.func(x))) # compose 'em
def __rmul__(self, other): # in case other is on the left
"""
multiply two Composers i.e. (f * g)(x) == f(g(x))
f might might a function. OK if g is Composer.
"""
if isinstance(other, types.FunctionType): # OK if target is a function
other = Composable(other)
if not isinstance(other, Composable): # by this point, other must be a Composer
raise TypeError
return Composable(lambda x: other.func(self.func(x))) # compose 'em
def __pow__(self, exp):
"""
A function may compose with itself why not?
"""
# type checking: we want a non-negative integer
if not isinstance(exp, int):
raise TypeError
if not exp > -1:
raise ValueError
me = self
if exp == 0: # corner case
return Composable(lambda x: x) # identify function
elif exp == 1:
return me # (f**1) == f
for _ in range(exp-1): # e.g. once around loop if exp==2
me = me * self
return me
def __repr__(self):
return "Composable({})".format(self.func.__name__)
@Composable
def f(x):
"second powering"
return x ** 2
@Composable
def g(x):
"adding 2"
return x + 2
print("(f * g)(7):", (f * g)(7)) # add 2 then 2nd power
print("(g * g)(7):", (g * f)(7)) # 2nd power then add 2
Suppose we wish to develop unit tests for such as the above, to show ourselves the intended functionality. Here's an example test suite:
In [2]:
import unittest
import sys
class TestComposer(unittest.TestCase):
def test_simple(self):
x = 5
self.assertEqual((f*g*g*f*g*f)(x), f(g(g(f(g(f(x)))))), "Not same!")
def test_function(self):
def addA(s): # not decorated
return s + "A"
@Composable
def addM(s):
return s + "M"
addAM = addM * addA # Composable times regular function, OK?
self.assertEqual(addAM("I "), "I AM", "appends A then M")
addMA = addA * addM # regular function, times Composable OK?
self.assertEqual(addMA("HI "), "HI MA", "appends M then A")
def test_inputs(self):
@Composable
def f(x):
"second powering"
return x ** 2
self.assertRaises(TypeError, f.__pow__, 2.0) # float not OK!
self.assertRaises(TypeError, f.__pow__, g) # another function? No!
self.assertRaises(ValueError, f.__pow__, -1) # negative number? No!
def test_powering(self):
@Composable
def f(x):
"second powering"
return x ** 2
@Composable
def g(x):
"adding 2"
return x + 2
self.assertEqual((f*f)(10), 10000, "2nd power of 2nd power")
self.assertEqual(pow(f, 3)(4), f(f(f(4))), "Powering broken")
h = (f**3) * (g**2)
self.assertEqual(h(-11), f(f(f(g(g(-11))))), "Powering broken")
self.assertEqual((f**0)(100), 100, "Identity function")
the_tests = TestComposer()
suite = unittest.TestLoader().loadTestsFromModule(the_tests)
output = unittest.TextTestRunner(stream=sys.stdout).run(suite)
if output.wasSuccessful():
print("All tests passed!")
We're good!
Algebra City series. By Kirby Urner (copyleft) MIT License, 2016