# -*- coding: utf-8 -*- """ Created on Thu Feb 11 12:07:59 2016
@author: Kirby Urner In algebra, we have the idea that functions might compose. We have this idea in POSIX too, i.e. the pipeline operator: $ ls *.py | wc -l The ls 'function' sends its output to wordcount -lines to say how many Python modules in a give directory. Instead of using a | (pipe) to chain or pipeline functions, lets overload the multiplication and powering operators * and **. Remember how lambda works to create a piece of functionality, of one expression? lambda x: f(g(x)) leaves the argument open i.e. we have not committed to any special x. lambda comes in handy below The unittests would usually be split off into a separate file, leaving algebra_city.py unencumbered with testing code, but for now we develop them as one. """ import unittest import types # <-- to get FunctionType class Composer: """ Composer swallows a function, which may still be called, by calling the instance instead. Used as a decorator, the Composer class enables composition of functions by means of multiplying and powering their corresponding Composer 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 Composers i.e. (f * g)(x) == f(g(x)) g might might a function. OK if f is Composer. """ if isinstance(other, types.FunctionType): # OK if target is a function other = Composer(other) if not isinstance(other, Composer): # by this point, other must be one raise TypeError return Composer(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 = Composer(other) if not isinstance(other, Composer): # by this point, other must be one raise TypeError return Composer(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 Composer(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 "Composer({})".format(self.func.__name__) @Composer def f(x): "second powering" return x ** 2 @Composer def g(x): "magnifying by 10" return x * 10 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" @Composer def addM(s): return s + "M" addAM = addM * addA # Composer times regular function, OK? self.assertEqual(addAM("I "), "I AM", "appends A then M") addMA = addA * addM # regular function, times Composer OK? self.assertEqual(addMA("HI "), "HI MA", "appends M then A") def test_inputs(self): 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): self.assertEqual((f*f)(10), 10000, "2nd power of 2nd power") self.assertEqual((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") if __name__ == "__main__": unittest.main()
_______________________________________________ Edu-sig mailing list Edu-sig@python.org https://mail.python.org/mailman/listinfo/edu-sig