[Edu-sig] from tonight's subject matter: algebra_city.py
kirby urner
kirby.urner at gmail.com
Thu Feb 11 19:33:57 EST 2016
# -*- 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()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/edu-sig/attachments/20160211/757dd8fb/attachment.html>
More information about the Edu-sig
mailing list